- const: lvds-encoder # Generic LVDS encoder compatible fallback
- items:
- enum:
+ - ti,ds90cf364a # For the DS90CF364A FPD-Link LVDS Receiver
- ti,ds90cf384a # For the DS90CF384A FPD-Link LVDS Receiver
- const: lvds-decoder # Generic LVDS decoders compatible fallback
- enum:
power-supply: true
-if:
- not:
- properties:
- compatible:
- contains:
- const: lvds-decoder
-then:
- properties:
- ports:
+allOf:
+ - if:
+ not:
+ properties:
+ compatible:
+ contains:
+ const: lvds-decoder
+ then:
properties:
- port@0:
+ ports:
properties:
- endpoint:
+ port@0:
properties:
- data-mapping: false
+ endpoint:
+ properties:
+ data-mapping: false
-if:
- not:
- properties:
- compatible:
- contains:
- const: lvds-encoder
-then:
- properties:
- pclk-sample: false
+ - if:
+ not:
+ properties:
+ compatible:
+ contains:
+ const: lvds-encoder
+ then:
+ properties:
+ pclk-sample: false
required:
- compatible
static int via_configure_agp3(void)
{
u32 temp;
- struct aper_size_info_16 *current_size;
-
- current_size = A_SIZE_16(agp_bridge->current_size);
/* address to map to */
agp_bridge->gart_bus_addr = pci_bus_address(agp_bridge->dev,
agp_put_bridge(bridge);
}
-#ifdef CONFIG_PM
+#define agp_via_suspend NULL
-static int agp_via_suspend(struct pci_dev *pdev, pm_message_t state)
+static int __maybe_unused agp_via_resume(struct device *dev)
{
- pci_save_state (pdev);
- pci_set_power_state (pdev, PCI_D3hot);
-
- return 0;
-}
-
-static int agp_via_resume(struct pci_dev *pdev)
-{
- struct agp_bridge_data *bridge = pci_get_drvdata(pdev);
-
- pci_set_power_state (pdev, PCI_D0);
- pci_restore_state(pdev);
+ struct agp_bridge_data *bridge = dev_get_drvdata(dev);
if (bridge->driver == &via_agp3_driver)
return via_configure_agp3();
return 0;
}
-#endif /* CONFIG_PM */
-
/* must be the same order as name table above */
static const struct pci_device_id agp_via_pci_table[] = {
#define ID(x) \
MODULE_DEVICE_TABLE(pci, agp_via_pci_table);
+static SIMPLE_DEV_PM_OPS(agp_via_pm_ops, agp_via_suspend, agp_via_resume);
static struct pci_driver agp_via_pci_driver = {
.name = "agpgart-via",
.id_table = agp_via_pci_table,
.probe = agp_via_probe,
.remove = agp_via_remove,
-#ifdef CONFIG_PM
- .suspend = agp_via_suspend,
- .resume = agp_via_resume,
-#endif
+ .driver.pm = &agp_via_pm_ops,
};
/* setup IORESOURCE_MEM as framebuffer memory */
memset(&res, 0, sizeof(res));
- res.flags = IORESOURCE_MEM | IORESOURCE_BUSY;
+ res.flags = IORESOURCE_MEM;
res.name = simplefb_resname;
res.start = base;
res.end = res.start + length - 1;
sysfb_apply_efi_quirks(pd);
ret = platform_device_add_resources(pd, &res, 1);
- if (ret)
+ if (ret) {
+ platform_device_put(pd);
return ret;
+ }
ret = platform_device_add_data(pd, mode, sizeof(*mode));
- if (ret)
+ if (ret) {
+ platform_device_put(pd);
return ret;
+ }
return platform_device_add(pd);
}
#include <drm/drm_edid.h>
#include <drm/drm_fb_helper.h>
- #include <drm/drm_dp_helper.h>
+ #include <drm/dp/drm_dp_helper.h>
#include <drm/drm_probe_helper.h>
#include <drm/amdgpu_drm.h>
#include "amdgpu.h"
case DRM_MODE_CONNECTOR_DVII:
case DRM_MODE_CONNECTOR_HDMIB:
if (amdgpu_connector->use_digital) {
- if (drm_detect_hdmi_monitor(amdgpu_connector_edid(connector))) {
+ if (connector->display_info.is_hdmi) {
if (connector->display_info.bpc)
bpc = connector->display_info.bpc;
}
break;
case DRM_MODE_CONNECTOR_DVID:
case DRM_MODE_CONNECTOR_HDMIA:
- if (drm_detect_hdmi_monitor(amdgpu_connector_edid(connector))) {
+ if (connector->display_info.is_hdmi) {
if (connector->display_info.bpc)
bpc = connector->display_info.bpc;
}
dig_connector = amdgpu_connector->con_priv;
if ((dig_connector->dp_sink_type == CONNECTOR_OBJECT_ID_DISPLAYPORT) ||
(dig_connector->dp_sink_type == CONNECTOR_OBJECT_ID_eDP) ||
- drm_detect_hdmi_monitor(amdgpu_connector_edid(connector))) {
+ connector->display_info.is_hdmi) {
if (connector->display_info.bpc)
bpc = connector->display_info.bpc;
}
break;
}
- if (drm_detect_hdmi_monitor(amdgpu_connector_edid(connector))) {
+ if (connector->display_info.is_hdmi) {
/*
* Pre DCE-8 hw can't handle > 12 bpc, and more than 12 bpc doesn't make
* much sense without support for > 12 bpc framebuffers. RGB 4:4:4 at
/* Check if bpc is within clock limit. Try to degrade gracefully otherwise */
if ((bpc == 12) && (mode_clock * 3/2 > max_tmds_clock)) {
- if ((connector->display_info.edid_hdmi_dc_modes & DRM_EDID_HDMI_DC_30) &&
+ if ((connector->display_info.edid_hdmi_rgb444_dc_modes & DRM_EDID_HDMI_DC_30) &&
(mode_clock * 5/4 <= max_tmds_clock))
bpc = 10;
else
if (!amdgpu_connector->edid) {
/* some laptops provide a hardcoded edid in rom for LCDs */
if (((connector->connector_type == DRM_MODE_CONNECTOR_LVDS) ||
- (connector->connector_type == DRM_MODE_CONNECTOR_eDP)))
+ (connector->connector_type == DRM_MODE_CONNECTOR_eDP))) {
amdgpu_connector->edid = amdgpu_connector_get_hardcoded_edid(adev);
+ drm_connector_update_edid_property(connector, amdgpu_connector->edid);
+ }
}
}
kfree(amdgpu_connector->edid);
amdgpu_connector->edid = NULL;
+ drm_connector_update_edid_property(connector, NULL);
}
static int amdgpu_connector_ddc_get_modes(struct drm_connector *connector)
native_mode->vdisplay != 0 &&
native_mode->clock != 0) {
mode = drm_mode_duplicate(dev, native_mode);
+ if (!mode)
+ return NULL;
+
mode->type = DRM_MODE_TYPE_PREFERRED | DRM_MODE_TYPE_DRIVER;
drm_mode_set_name(mode);
* simpler.
*/
mode = drm_cvt_mode(dev, native_mode->hdisplay, native_mode->vdisplay, 60, true, false, false);
+ if (!mode)
+ return NULL;
+
mode->type = DRM_MODE_TYPE_PREFERRED | DRM_MODE_TYPE_DRIVER;
DRM_DEBUG_KMS("Adding cvt approximation of native panel mode %s\n", mode->name);
}
(amdgpu_connector->connector_object_id == CONNECTOR_OBJECT_ID_DUAL_LINK_DVI_D) ||
(amdgpu_connector->connector_object_id == CONNECTOR_OBJECT_ID_HDMI_TYPE_B)) {
return MODE_OK;
- } else if (drm_detect_hdmi_monitor(amdgpu_connector_edid(connector))) {
+ } else if (connector->display_info.is_hdmi) {
/* HDMI 1.3+ supports max clock of 340 Mhz */
if (mode->clock > 340000)
return MODE_CLOCK_HIGH;
(amdgpu_dig_connector->dp_sink_type == CONNECTOR_OBJECT_ID_eDP)) {
return amdgpu_atombios_dp_mode_valid_helper(connector, mode);
} else {
- if (drm_detect_hdmi_monitor(amdgpu_connector_edid(connector))) {
+ if (connector->display_info.is_hdmi) {
/* HDMI 1.3+ supports max clock of 340 Mhz */
if (mode->clock > 340000)
return MODE_CLOCK_HIGH;
{
s64 time_us, increment_us;
u64 free_vram, total_vram, used_vram;
- struct ttm_resource_manager *vram_man = ttm_manager_type(&adev->mman.bdev, TTM_PL_VRAM);
/* Allow a maximum of 200 accumulated ms. This is basically per-IB
* throttling.
*
}
total_vram = adev->gmc.real_vram_size - atomic64_read(&adev->vram_pin_size);
- used_vram = amdgpu_vram_mgr_usage(vram_man);
+ used_vram = amdgpu_vram_mgr_usage(&adev->mman.vram_mgr);
free_vram = used_vram >= total_vram ? 0 : total_vram - used_vram;
spin_lock(&adev->mm_stats.lock);
if (!amdgpu_gmc_vram_full_visible(&adev->gmc)) {
u64 total_vis_vram = adev->gmc.visible_vram_size;
u64 used_vis_vram =
- amdgpu_vram_mgr_vis_usage(vram_man);
+ amdgpu_vram_mgr_vis_usage(&adev->mman.vram_mgr);
if (used_vis_vram < total_vis_vram) {
u64 free_vis_vram = total_vis_vram - used_vis_vram;
/*
* Work around dma_resv shortcommings by wrapping up the
* submission in a dma_fence_chain and add it as exclusive
- * fence, but first add the submission as shared fence to make
- * sure that shared fences never signal before the exclusive
- * one.
+ * fence.
*/
dma_fence_chain_init(chain, dma_resv_excl_fence(resv),
dma_fence_get(p->fence), 1);
- dma_resv_add_shared_fence(resv, p->fence);
rcu_assign_pointer(resv->fence_excl, &chain->base);
e->chain = NULL;
}
goto unpin;
}
- r = dma_resv_get_fences(new_abo->tbo.base.resv, NULL,
- &work->shared_count, &work->shared);
+ /* TODO: Unify this with other drivers */
+ r = dma_resv_get_fences(new_abo->tbo.base.resv, true,
+ &work->shared_count,
+ &work->shared);
if (unlikely(r != 0)) {
DRM_ERROR("failed to get fences for buffer\n");
goto unpin;
if ((!(mode->flags & DRM_MODE_FLAG_INTERLACE)) &&
((amdgpu_encoder->underscan_type == UNDERSCAN_ON) ||
((amdgpu_encoder->underscan_type == UNDERSCAN_AUTO) &&
- drm_detect_hdmi_monitor(amdgpu_connector_edid(connector)) &&
+ connector->display_info.is_hdmi &&
amdgpu_display_is_hdtv_mode(mode)))) {
if (amdgpu_encoder->underscan_hborder != 0)
amdgpu_crtc->h_border = amdgpu_encoder->underscan_hborder;
{
struct drm_device *ddev = dev_get_drvdata(dev);
struct amdgpu_device *adev = drm_to_adev(ddev);
- struct ttm_resource_manager *man;
- man = ttm_manager_type(&adev->mman.bdev, TTM_PL_TT);
- return sysfs_emit(buf, "%llu\n", amdgpu_gtt_mgr_usage(man));
+ return sysfs_emit(buf, "%llu\n", amdgpu_gtt_mgr_usage(&adev->mman.gtt_mgr));
}
static DEVICE_ATTR(mem_info_gtt_total, S_IRUGO,
return 0;
err_free:
+ ttm_resource_fini(man, &node->base.base);
kfree(node);
err_out:
if (!(res->placement & TTM_PL_FLAG_TEMPORARY))
atomic64_sub(res->num_pages, &mgr->used);
+ ttm_resource_fini(man, res);
kfree(node);
}
/**
* amdgpu_gtt_mgr_usage - return usage of GTT domain
*
- * @man: TTM memory type manager
+ * @mgr: amdgpu_gtt_mgr pointer
*
* Return how many bytes are used in the GTT domain
*/
-uint64_t amdgpu_gtt_mgr_usage(struct ttm_resource_manager *man)
+uint64_t amdgpu_gtt_mgr_usage(struct amdgpu_gtt_mgr *mgr)
{
- struct amdgpu_gtt_mgr *mgr = to_gtt_mgr(man);
-
return atomic64_read(&mgr->used) * PAGE_SIZE;
}
/**
* amdgpu_gtt_mgr_recover - re-init gart
*
- * @man: TTM memory type manager
+ * @mgr: amdgpu_gtt_mgr pointer
*
* Re-init the gart for each known BO in the GTT.
*/
-int amdgpu_gtt_mgr_recover(struct ttm_resource_manager *man)
+int amdgpu_gtt_mgr_recover(struct amdgpu_gtt_mgr *mgr)
{
- struct amdgpu_gtt_mgr *mgr = to_gtt_mgr(man);
- struct amdgpu_device *adev;
struct amdgpu_gtt_node *node;
struct drm_mm_node *mm_node;
+ struct amdgpu_device *adev;
int r = 0;
adev = container_of(mgr, typeof(*adev), mman.gtt_mgr);
man->use_tt = true;
man->func = &amdgpu_gtt_mgr_func;
- ttm_resource_manager_init(man, gtt_size >> PAGE_SHIFT);
+ ttm_resource_manager_init(man, &adev->mman.bdev,
+ gtt_size >> PAGE_SHIFT);
start = AMDGPU_GTT_MAX_TRANSFER_SIZE * AMDGPU_GTT_NUM_TRANSFER_WINDOWS;
size = (adev->gmc.gart_size >> PAGE_SHIFT) - start;
* @man: TTM memory type manager
* @tbo: TTM BO we need this range for
* @place: placement flags and restrictions
- * @mem: the resulting mem object
+ * @res: TTM memory object
*
* Dummy, just count the space used without allocating resources or any limit.
*/
* amdgpu_preempt_mgr_del - free ranges
*
* @man: TTM memory type manager
- * @mem: TTM memory object
+ * @res: TTM memory object
*
* Free the allocated GTT again.
*/
struct amdgpu_preempt_mgr *mgr = to_preempt_mgr(man);
atomic64_sub(res->num_pages, &mgr->used);
+ ttm_resource_fini(man, res);
kfree(res);
}
man->use_tt = true;
man->func = &amdgpu_preempt_mgr_func;
- ttm_resource_manager_init(man, (1 << 30));
+ ttm_resource_manager_init(man, &adev->mman.bdev, (1 << 30));
atomic64_set(&mgr->used, 0);
#include <linux/sizes.h>
#include <linux/module.h>
+#include <drm/drm_drv.h>
#include <drm/ttm/ttm_bo_api.h>
#include <drm/ttm/ttm_bo_driver.h>
#include <drm/ttm/ttm_placement.h>
*/
void amdgpu_ttm_fini(struct amdgpu_device *adev)
{
+ int idx;
if (!adev->mman.initialized)
return;
NULL, NULL);
amdgpu_ttm_fw_reserve_vram_fini(adev);
+ if (drm_dev_enter(adev_to_drm(adev), &idx)) {
+
+ if (adev->mman.aper_base_kaddr)
+ iounmap(adev->mman.aper_base_kaddr);
+ adev->mman.aper_base_kaddr = NULL;
+
+ drm_dev_exit(idx);
+ }
+
amdgpu_vram_mgr_fini(adev);
amdgpu_gtt_mgr_fini(adev);
amdgpu_preempt_mgr_fini(adev);
TTM_PL_VRAM);
struct drm_printer p = drm_seq_file_printer(m);
- man->func->debug(man, &p);
+ ttm_resource_manager_debug(man, &p);
return 0;
}
TTM_PL_TT);
struct drm_printer p = drm_seq_file_printer(m);
- man->func->debug(man, &p);
+ ttm_resource_manager_debug(man, &p);
return 0;
}
AMDGPU_PL_GDS);
struct drm_printer p = drm_seq_file_printer(m);
- man->func->debug(man, &p);
+ ttm_resource_manager_debug(man, &p);
return 0;
}
AMDGPU_PL_GWS);
struct drm_printer p = drm_seq_file_printer(m);
- man->func->debug(man, &p);
+ ttm_resource_manager_debug(man, &p);
return 0;
}
AMDGPU_PL_OA);
struct drm_printer p = drm_seq_file_printer(m);
- man->func->debug(man, &p);
+ ttm_resource_manager_debug(man, &p);
return 0;
}
{
struct drm_device *ddev = dev_get_drvdata(dev);
struct amdgpu_device *adev = drm_to_adev(ddev);
- struct ttm_resource_manager *man;
- man = ttm_manager_type(&adev->mman.bdev, TTM_PL_VRAM);
- return sysfs_emit(buf, "%llu\n", amdgpu_vram_mgr_usage(man));
+ return sysfs_emit(buf, "%llu\n",
+ amdgpu_vram_mgr_usage(&adev->mman.vram_mgr));
}
/**
{
struct drm_device *ddev = dev_get_drvdata(dev);
struct amdgpu_device *adev = drm_to_adev(ddev);
- struct ttm_resource_manager *man;
- man = ttm_manager_type(&adev->mman.bdev, TTM_PL_VRAM);
- return sysfs_emit(buf, "%llu\n", amdgpu_vram_mgr_vis_usage(man));
+ return sysfs_emit(buf, "%llu\n",
+ amdgpu_vram_mgr_vis_usage(&adev->mman.vram_mgr));
}
/**
/**
* amdgpu_vram_mgr_reserve_range - Reserve a range from VRAM
*
- * @man: TTM memory type manager
+ * @mgr: amdgpu_vram_mgr pointer
* @start: start address of the range in VRAM
* @size: size of the range
*
- * Reserve memory from start addess with the specified size in VRAM
+ * Reserve memory from start address with the specified size in VRAM
*/
-int amdgpu_vram_mgr_reserve_range(struct ttm_resource_manager *man,
+int amdgpu_vram_mgr_reserve_range(struct amdgpu_vram_mgr *mgr,
uint64_t start, uint64_t size)
{
- struct amdgpu_vram_mgr *mgr = to_vram_mgr(man);
struct amdgpu_vram_reservation *rsv;
rsv = kzalloc(sizeof(*rsv), GFP_KERNEL);
spin_lock(&mgr->lock);
list_add_tail(&mgr->reservations_pending, &rsv->node);
- amdgpu_vram_mgr_do_reserve(man);
+ amdgpu_vram_mgr_do_reserve(&mgr->manager);
spin_unlock(&mgr->lock);
return 0;
/**
* amdgpu_vram_mgr_query_page_status - query the reservation status
*
- * @man: TTM memory type manager
+ * @mgr: amdgpu_vram_mgr pointer
* @start: start address of a page in VRAM
*
* Returns:
* 0: the page has been reserved
* -ENOENT: the input page is not a reservation
*/
-int amdgpu_vram_mgr_query_page_status(struct ttm_resource_manager *man,
+int amdgpu_vram_mgr_query_page_status(struct amdgpu_vram_mgr *mgr,
uint64_t start)
{
- struct amdgpu_vram_mgr *mgr = to_vram_mgr(man);
struct amdgpu_vram_reservation *rsv;
int ret;
while (i--)
drm_mm_remove_node(&node->mm_nodes[i]);
spin_unlock(&mgr->lock);
+ ttm_resource_fini(man, &node->base);
kvfree(node);
error_sub:
atomic64_sub(usage, &mgr->usage);
atomic64_sub(vis_usage, &mgr->vis_usage);
+ ttm_resource_fini(man, res);
kvfree(node);
}
/**
* amdgpu_vram_mgr_usage - how many bytes are used in this domain
*
- * @man: TTM memory type manager
+ * @mgr: amdgpu_vram_mgr pointer
*
* Returns how many bytes are used in this domain.
*/
-uint64_t amdgpu_vram_mgr_usage(struct ttm_resource_manager *man)
+uint64_t amdgpu_vram_mgr_usage(struct amdgpu_vram_mgr *mgr)
{
- struct amdgpu_vram_mgr *mgr = to_vram_mgr(man);
-
return atomic64_read(&mgr->usage);
}
/**
* amdgpu_vram_mgr_vis_usage - how many bytes are used in the visible part
*
- * @man: TTM memory type manager
+ * @mgr: amdgpu_vram_mgr pointer
*
* Returns how many bytes are used in the visible part of VRAM
*/
-uint64_t amdgpu_vram_mgr_vis_usage(struct ttm_resource_manager *man)
+uint64_t amdgpu_vram_mgr_vis_usage(struct amdgpu_vram_mgr *mgr)
{
- struct amdgpu_vram_mgr *mgr = to_vram_mgr(man);
-
return atomic64_read(&mgr->vis_usage);
}
spin_unlock(&mgr->lock);
drm_printf(printer, "man size:%llu pages, ram usage:%lluMB, vis usage:%lluMB\n",
- man->size, amdgpu_vram_mgr_usage(man) >> 20,
- amdgpu_vram_mgr_vis_usage(man) >> 20);
+ man->size, amdgpu_vram_mgr_usage(mgr) >> 20,
+ amdgpu_vram_mgr_vis_usage(mgr) >> 20);
}
static const struct ttm_resource_manager_func amdgpu_vram_mgr_func = {
struct amdgpu_vram_mgr *mgr = &adev->mman.vram_mgr;
struct ttm_resource_manager *man = &mgr->manager;
- ttm_resource_manager_init(man, adev->gmc.real_vram_size >> PAGE_SHIFT);
+ ttm_resource_manager_init(man, &adev->mman.bdev,
+ adev->gmc.real_vram_size >> PAGE_SHIFT);
man->func = &amdgpu_vram_mgr_func;
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_uapi.h>
#include <drm/drm_atomic_helper.h>
- #include <drm/drm_dp_mst_helper.h>
+ #include <drm/dp/drm_dp_mst_helper.h>
#include <drm/drm_fb_helper.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_edid.h>
#endif /* CONFIG_DRM_AMD_SECURE_DISPLAY */
/**
- * dmub_aux_setconfig_reply_callback - Callback for AUX or SET_CONFIG command.
+ * dmub_aux_setconfig_callback - Callback for AUX or SET_CONFIG command.
* @adev: amdgpu_device pointer
* @notify: dmub notification structure
*
* Copies dmub notification to DM which is to be read by AUX command.
* issuing thread and also signals the event to wake up the thread.
*/
-void dmub_aux_setconfig_callback(struct amdgpu_device *adev, struct dmub_notification *notify)
+static void dmub_aux_setconfig_callback(struct amdgpu_device *adev,
+ struct dmub_notification *notify)
{
if (adev->dm.dmub_notify)
memcpy(adev->dm.dmub_notify, notify, sizeof(struct dmub_notification));
* Dmub Hpd interrupt processing callback. Gets displayindex through the
* ink index and calls helper to do the processing.
*/
-void dmub_hpd_callback(struct amdgpu_device *adev, struct dmub_notification *notify)
+static void dmub_hpd_callback(struct amdgpu_device *adev,
+ struct dmub_notification *notify)
{
struct amdgpu_dm_connector *aconnector;
struct amdgpu_dm_connector *hpd_aconnector = NULL;
struct drm_connector_list_iter iter;
struct dc_link *link;
uint8_t link_index = 0;
- struct drm_device *dev = adev->dm.ddev;
+ struct drm_device *dev;
if (adev == NULL)
return;
link_index = notify->link_index;
link = adev->dm.dc->links[link_index];
+ dev = adev->dm.ddev;
drm_connector_list_iter_begin(dev, &iter);
drm_for_each_connector_iter(connector, &iter) {
* to dmub interrupt handling thread
* Return: true if successfully registered, false if there is existing registration
*/
-bool register_dmub_notify_callback(struct amdgpu_device *adev, enum dmub_notification_type type,
-dmub_notify_interrupt_callback_t callback, bool dmub_int_thread_offload)
+static bool register_dmub_notify_callback(struct amdgpu_device *adev,
+ enum dmub_notification_type type,
+ dmub_notify_interrupt_callback_t callback,
+ bool dmub_int_thread_offload)
{
if (callback != NULL && type < ARRAY_SIZE(adev->dm.dmub_thread_offload)) {
adev->dm.dmub_callback[type] = callback;
return 0;
}
+ /* Reset DMCUB if it was previously running - before we overwrite its memory. */
+ status = dmub_srv_hw_reset(dmub_srv);
+ if (status != DMUB_STATUS_OK)
+ DRM_WARN("Error resetting DMUB HW: %d\n", status);
+
hdr = (const struct dmcub_firmware_header_v1_0 *)dmub_fw->data;
fw_inst_const = dmub_fw->data +
return 0;
}
+static void dm_dmub_hw_resume(struct amdgpu_device *adev)
+{
+ struct dmub_srv *dmub_srv = adev->dm.dmub_srv;
+ enum dmub_status status;
+ bool init;
+
+ if (!dmub_srv) {
+ /* DMUB isn't supported on the ASIC. */
+ return;
+ }
+
+ status = dmub_srv_is_hw_init(dmub_srv, &init);
+ if (status != DMUB_STATUS_OK)
+ DRM_WARN("DMUB hardware init check failed: %d\n", status);
+
+ if (status == DMUB_STATUS_OK && init) {
+ /* Wait for firmware load to finish. */
+ status = dmub_srv_wait_for_auto_load(dmub_srv, 100000);
+ if (status != DMUB_STATUS_OK)
+ DRM_WARN("Wait for DMUB auto-load failed: %d\n", status);
+ } else {
+ /* Perform the full hardware initialization. */
+ dm_dmub_hw_init(adev);
+ }
+}
+
#if defined(CONFIG_DRM_AMD_DC_DCN)
static void mmhub_read_system_context(struct amdgpu_device *adev, struct dc_phy_addr_space_config *pa_config)
{
if (amdgpu_dc_feature_mask & DC_EDP_NO_POWER_SEQUENCING)
init_data.flags.edp_no_power_sequencing = true;
+#ifdef CONFIG_DRM_AMD_DC_DCN
+ if (amdgpu_dc_feature_mask & DC_DISABLE_LTTPR_DP1_4A)
+ init_data.flags.allow_lttpr_non_transparent_mode.bits.DP1_4A = true;
+ if (amdgpu_dc_feature_mask & DC_DISABLE_LTTPR_DP2_0)
+ init_data.flags.allow_lttpr_non_transparent_mode.bits.DP2_0 = true;
+#endif
+
init_data.flags.power_down_display_on_boot = true;
if (check_seamless_boot_capability(adev)) {
amdgpu_dm_outbox_init(adev);
/* Before powering on DC we need to re-initialize DMUB. */
- r = dm_dmub_hw_init(adev);
- if (r)
- DRM_ERROR("DMUB interface failed to initialize: status=%d\n", r);
+ dm_dmub_hw_resume(adev);
/* power on hardware */
dc_set_power_state(dm->dc, DC_ACPI_CM_POWER_STATE_D0);
aconnector->edid =
(struct edid *)sink->dc_edid.raw_edid;
- drm_connector_update_edid_property(connector,
- aconnector->edid);
if (aconnector->dc_link->aux_mode)
drm_dp_cec_set_edid(&aconnector->dm_dp_aux.aux,
aconnector->edid);
}
+ drm_connector_update_edid_property(connector, aconnector->edid);
amdgpu_dm_update_freesync_caps(connector, aconnector->edid);
update_connector_ext_caps(aconnector);
} else {
else if (drm_mode_is_420_also(info, mode_in)
&& aconnector->force_yuv420_output)
timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR420;
- else if ((connector->display_info.color_formats & DRM_COLOR_FORMAT_YCRCB444)
+ else if ((connector->display_info.color_formats & DRM_COLOR_FORMAT_YCBCR444)
&& stream->signal == SIGNAL_TYPE_HDMI_TYPE_A)
timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR444;
else
struct dsc_dec_dpcd_caps *dsc_caps)
{
stream->timing.flags.DSC = 0;
+ dsc_caps->is_dsc_supported = false;
if (aconnector->dc_link && (sink->sink_signal == SIGNAL_TYPE_DISPLAY_PORT ||
sink->sink_signal == SIGNAL_TYPE_EDP)) {
- dc_dsc_parse_dsc_dpcd(aconnector->dc_link->ctx->dc,
- aconnector->dc_link->dpcd_caps.dsc_caps.dsc_basic_caps.raw,
- aconnector->dc_link->dpcd_caps.dsc_caps.dsc_branch_decoder_caps.raw,
- dsc_caps);
+ if (sink->link->dpcd_caps.dongle_type == DISPLAY_DONGLE_NONE ||
+ sink->link->dpcd_caps.dongle_type == DISPLAY_DONGLE_DP_HDMI_CONVERTER)
+ dc_dsc_parse_dsc_dpcd(aconnector->dc_link->ctx->dc,
+ aconnector->dc_link->dpcd_caps.dsc_caps.dsc_basic_caps.raw,
+ aconnector->dc_link->dpcd_caps.dsc_caps.dsc_branch_decoder_caps.raw,
+ dsc_caps);
}
}
uint32_t link_bandwidth_kbps;
uint32_t max_dsc_target_bpp_limit_override = 0;
struct dc *dc = sink->ctx->dc;
+ uint32_t max_supported_bw_in_kbps, timing_bw_in_kbps;
+ uint32_t dsc_max_supported_bw_in_kbps;
link_bandwidth_kbps = dc_link_bandwidth_kbps(aconnector->dc_link,
dc_link_get_link_cap(aconnector->dc_link));
apply_dsc_policy_for_edp(aconnector, sink, stream, dsc_caps, max_dsc_target_bpp_limit_override);
} else if (aconnector->dc_link && sink->sink_signal == SIGNAL_TYPE_DISPLAY_PORT) {
-
- if (dc_dsc_compute_config(aconnector->dc_link->ctx->dc->res_pool->dscs[0],
+ if (sink->link->dpcd_caps.dongle_type == DISPLAY_DONGLE_NONE) {
+ if (dc_dsc_compute_config(aconnector->dc_link->ctx->dc->res_pool->dscs[0],
dsc_caps,
aconnector->dc_link->ctx->dc->debug.dsc_min_slice_height_override,
max_dsc_target_bpp_limit_override,
link_bandwidth_kbps,
&stream->timing,
&stream->timing.dsc_cfg)) {
- stream->timing.flags.DSC = 1;
- DRM_DEBUG_DRIVER("%s: [%s] DSC is selected from SST RX\n", __func__, drm_connector->name);
+ stream->timing.flags.DSC = 1;
+ DRM_DEBUG_DRIVER("%s: [%s] DSC is selected from SST RX\n",
+ __func__, drm_connector->name);
+ }
+ } else if (sink->link->dpcd_caps.dongle_type == DISPLAY_DONGLE_DP_HDMI_CONVERTER) {
+ timing_bw_in_kbps = dc_bandwidth_in_kbps_from_timing(&stream->timing);
+ max_supported_bw_in_kbps = link_bandwidth_kbps;
+ dsc_max_supported_bw_in_kbps = link_bandwidth_kbps;
+
+ if (timing_bw_in_kbps > max_supported_bw_in_kbps &&
+ max_supported_bw_in_kbps > 0 &&
+ dsc_max_supported_bw_in_kbps > 0)
+ if (dc_dsc_compute_config(aconnector->dc_link->ctx->dc->res_pool->dscs[0],
+ dsc_caps,
+ aconnector->dc_link->ctx->dc->debug.dsc_min_slice_height_override,
+ max_dsc_target_bpp_limit_override,
+ dsc_max_supported_bw_in_kbps,
+ &stream->timing,
+ &stream->timing.dsc_cfg)) {
+ stream->timing.flags.DSC = 1;
+ DRM_DEBUG_DRIVER("%s: [%s] DSC is selected from DP-HDMI PCON\n",
+ __func__, drm_connector->name);
+ }
}
}
break;
case DRM_MODE_CONNECTOR_DisplayPort:
aconnector->base.polled = DRM_CONNECTOR_POLL_HPD;
- if (link->is_dig_mapping_flexible &&
- link->dc->res_pool->funcs->link_encs_assign) {
- link->link_enc =
- link_enc_cfg_get_link_enc_used_by_link(link->ctx->dc, link);
- if (!link->link_enc)
- link->link_enc =
- link_enc_cfg_get_next_avail_link_enc(link->ctx->dc);
- }
-
+ link->link_enc = dp_get_link_enc(link);
+ ASSERT(link->link_enc);
if (link->link_enc)
aconnector->base.ycbcr_420_allowed =
link->link_enc->features.dp_ycbcr420_supported ? true : false;
dm_new_plane_state->dc_state = dc_new_plane_state;
+ dm_new_crtc_state->mpo_requested |= (plane->type == DRM_PLANE_TYPE_OVERLAY);
+
/* Tell DC to do a full surface update every time there
* is a plane change. Inefficient, but works for now.
*/
return ret;
}
+static void dm_get_oriented_plane_size(struct drm_plane_state *plane_state,
+ int *src_w, int *src_h)
+{
+ switch (plane_state->rotation & DRM_MODE_ROTATE_MASK) {
+ case DRM_MODE_ROTATE_90:
+ case DRM_MODE_ROTATE_270:
+ *src_w = plane_state->src_h >> 16;
+ *src_h = plane_state->src_w >> 16;
+ break;
+ case DRM_MODE_ROTATE_0:
+ case DRM_MODE_ROTATE_180:
+ default:
+ *src_w = plane_state->src_w >> 16;
+ *src_h = plane_state->src_h >> 16;
+ break;
+ }
+}
+
static int dm_check_crtc_cursor(struct drm_atomic_state *state,
struct drm_crtc *crtc,
struct drm_crtc_state *new_crtc_state)
struct drm_plane_state *new_cursor_state, *new_underlying_state;
int i;
int cursor_scale_w, cursor_scale_h, underlying_scale_w, underlying_scale_h;
+ int cursor_src_w, cursor_src_h;
+ int underlying_src_w, underlying_src_h;
/* On DCE and DCN there is no dedicated hardware cursor plane. We get a
* cursor per pipe but it's going to inherit the scaling and
return 0;
}
- cursor_scale_w = new_cursor_state->crtc_w * 1000 /
- (new_cursor_state->src_w >> 16);
- cursor_scale_h = new_cursor_state->crtc_h * 1000 /
- (new_cursor_state->src_h >> 16);
+ dm_get_oriented_plane_size(new_cursor_state, &cursor_src_w, &cursor_src_h);
+ cursor_scale_w = new_cursor_state->crtc_w * 1000 / cursor_src_w;
+ cursor_scale_h = new_cursor_state->crtc_h * 1000 / cursor_src_h;
for_each_new_plane_in_state_reverse(state, underlying, new_underlying_state, i) {
/* Narrow down to non-cursor planes on the same CRTC as the cursor */
if (!new_underlying_state->fb)
continue;
- underlying_scale_w = new_underlying_state->crtc_w * 1000 /
- (new_underlying_state->src_w >> 16);
- underlying_scale_h = new_underlying_state->crtc_h * 1000 /
- (new_underlying_state->src_h >> 16);
+ dm_get_oriented_plane_size(new_underlying_state,
+ &underlying_src_w, &underlying_src_h);
+ underlying_scale_w = new_underlying_state->crtc_w * 1000 / underlying_src_w;
+ underlying_scale_h = new_underlying_state->crtc_h * 1000 / underlying_src_h;
if (cursor_scale_w != underlying_scale_w ||
cursor_scale_h != underlying_scale_h) {
enum dc_status status;
int ret, i;
bool lock_and_validation_needed = false;
- struct dm_crtc_state *dm_old_crtc_state;
+ struct dm_crtc_state *dm_old_crtc_state, *dm_new_crtc_state;
#if defined(CONFIG_DRM_AMD_DC_DCN)
struct dsc_mst_fairness_vars vars[MAX_PIPES];
struct drm_dp_mst_topology_state *mst_state;
goto fail;
}
+ for_each_new_crtc_in_state(state, crtc, new_crtc_state, i) {
+ dm_new_crtc_state = to_dm_crtc_state(new_crtc_state);
+ if (dm_new_crtc_state->mpo_requested)
+ DRM_DEBUG_DRIVER("MPO enablement requested on crtc:[%p]\n", crtc);
+ }
+
/* Check cursor planes scaling */
for_each_new_crtc_in_state(state, crtc, new_crtc_state, i) {
ret = dm_check_crtc_cursor(state, crtc, new_crtc_state);
sizeof(cmd.edid_cea) - sizeof(cmd.edid_cea.header);
input->offset = offset;
input->length = length;
- input->total_length = total_length;
+ input->cea_total_length = total_length;
memcpy(input->payload, data, length);
res = dc_dmub_srv_cmd_with_reply_data(dm->dc->ctx->dmub_srv, &cmd);
return value;
}
-int amdgpu_dm_set_dmub_async_sync_status(bool is_cmd_aux, struct dc_context *ctx,
- uint8_t status_type, uint32_t *operation_result)
+static int amdgpu_dm_set_dmub_async_sync_status(bool is_cmd_aux,
+ struct dc_context *ctx,
+ uint8_t status_type,
+ uint32_t *operation_result)
{
struct amdgpu_device *adev = ctx->driver_context;
int return_status = -1;
#include <drm/drm_atomic.h>
#include <drm/drm_connector.h>
#include <drm/drm_crtc.h>
- #include <drm/drm_dp_mst_helper.h>
+ #include <drm/dp/drm_dp_mst_helper.h>
#include <drm/drm_plane.h>
/*
#define AMDGPU_DMUB_NOTIFICATION_MAX 5
-/**
+/*
* DMUB Async to Sync Mechanism Status
- **/
+ */
#define DMUB_ASYNC_TO_SYNC_ACCESS_FAIL 1
#define DMUB_ASYNC_TO_SYNC_ACCESS_TIMEOUT 2
#define DMUB_ASYNC_TO_SYNC_ACCESS_SUCCESS 3
bool cm_has_degamma;
bool cm_is_degamma_srgb;
+ bool mpo_requested;
+
int update_type;
int active_planes;
static void ast_set_crtthd_reg(struct ast_private *ast)
{
/* Set Threshold */
- if (ast->chip == AST2300 || ast->chip == AST2400 ||
+ if (ast->chip == AST2600) {
+ ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0xa7, 0xe0);
+ ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0xa6, 0xa0);
+ } else if (ast->chip == AST2300 || ast->chip == AST2400 ||
ast->chip == AST2500) {
ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0xa7, 0x78);
ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0xa6, 0x60);
if (crtc->state)
crtc->funcs->atomic_destroy_state(crtc, crtc->state);
- __drm_atomic_helper_crtc_reset(crtc, &ast_state->base);
+ if (ast_state)
+ __drm_atomic_helper_crtc_reset(crtc, &ast_state->base);
+ else
+ __drm_atomic_helper_crtc_reset(crtc, NULL);
}
static struct drm_crtc_state *
--- /dev/null
+ /*
+ * Copyright © 2014 Red Hat
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting documentation, and
+ * that the name of the copyright holders not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission. The copyright holders make no representations
+ * about the suitability of this software for any purpose. It is provided "as
+ * is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+ * OF THIS SOFTWARE.
+ */
+
+ #include <linux/bitfield.h>
+ #include <linux/delay.h>
+ #include <linux/errno.h>
+ #include <linux/i2c.h>
+ #include <linux/init.h>
+ #include <linux/kernel.h>
+ #include <linux/random.h>
+ #include <linux/sched.h>
+ #include <linux/seq_file.h>
+ #include <linux/iopoll.h>
+
+ #if IS_ENABLED(CONFIG_DRM_DEBUG_DP_MST_TOPOLOGY_REFS)
+ #include <linux/stacktrace.h>
+ #include <linux/sort.h>
+ #include <linux/timekeeping.h>
+ #include <linux/math64.h>
+ #endif
+
+ #include <drm/dp/drm_dp_mst_helper.h>
+ #include <drm/drm_atomic.h>
+ #include <drm/drm_atomic_helper.h>
+ #include <drm/drm_drv.h>
+ #include <drm/drm_print.h>
+ #include <drm/drm_probe_helper.h>
+
+ #include "drm_dp_helper_internal.h"
+ #include "drm_dp_mst_topology_internal.h"
+
+ /**
+ * DOC: dp mst helper
+ *
+ * These functions contain parts of the DisplayPort 1.2a MultiStream Transport
+ * protocol. The helpers contain a topology manager and bandwidth manager.
+ * The helpers encapsulate the sending and received of sideband msgs.
+ */
+ struct drm_dp_pending_up_req {
+ struct drm_dp_sideband_msg_hdr hdr;
+ struct drm_dp_sideband_msg_req_body msg;
+ struct list_head next;
+ };
+
+ static bool dump_dp_payload_table(struct drm_dp_mst_topology_mgr *mgr,
+ char *buf);
+
+ static void drm_dp_mst_topology_put_port(struct drm_dp_mst_port *port);
+
+ static int drm_dp_dpcd_write_payload(struct drm_dp_mst_topology_mgr *mgr,
+ int id,
+ struct drm_dp_payload *payload);
+
+ static int drm_dp_send_dpcd_read(struct drm_dp_mst_topology_mgr *mgr,
+ struct drm_dp_mst_port *port,
+ int offset, int size, u8 *bytes);
+ static int drm_dp_send_dpcd_write(struct drm_dp_mst_topology_mgr *mgr,
+ struct drm_dp_mst_port *port,
+ int offset, int size, u8 *bytes);
+
+ static int drm_dp_send_link_address(struct drm_dp_mst_topology_mgr *mgr,
+ struct drm_dp_mst_branch *mstb);
+
+ static void
+ drm_dp_send_clear_payload_id_table(struct drm_dp_mst_topology_mgr *mgr,
+ struct drm_dp_mst_branch *mstb);
+
+ static int drm_dp_send_enum_path_resources(struct drm_dp_mst_topology_mgr *mgr,
+ struct drm_dp_mst_branch *mstb,
+ struct drm_dp_mst_port *port);
+ static bool drm_dp_validate_guid(struct drm_dp_mst_topology_mgr *mgr,
+ u8 *guid);
+
+ static int drm_dp_mst_register_i2c_bus(struct drm_dp_mst_port *port);
+ static void drm_dp_mst_unregister_i2c_bus(struct drm_dp_mst_port *port);
+ static void drm_dp_mst_kick_tx(struct drm_dp_mst_topology_mgr *mgr);
+
+ static bool drm_dp_mst_port_downstream_of_branch(struct drm_dp_mst_port *port,
+ struct drm_dp_mst_branch *branch);
+
+ #define DBG_PREFIX "[dp_mst]"
+
+ #define DP_STR(x) [DP_ ## x] = #x
+
+ static const char *drm_dp_mst_req_type_str(u8 req_type)
+ {
+ static const char * const req_type_str[] = {
+ DP_STR(GET_MSG_TRANSACTION_VERSION),
+ DP_STR(LINK_ADDRESS),
+ DP_STR(CONNECTION_STATUS_NOTIFY),
+ DP_STR(ENUM_PATH_RESOURCES),
+ DP_STR(ALLOCATE_PAYLOAD),
+ DP_STR(QUERY_PAYLOAD),
+ DP_STR(RESOURCE_STATUS_NOTIFY),
+ DP_STR(CLEAR_PAYLOAD_ID_TABLE),
+ DP_STR(REMOTE_DPCD_READ),
+ DP_STR(REMOTE_DPCD_WRITE),
+ DP_STR(REMOTE_I2C_READ),
+ DP_STR(REMOTE_I2C_WRITE),
+ DP_STR(POWER_UP_PHY),
+ DP_STR(POWER_DOWN_PHY),
+ DP_STR(SINK_EVENT_NOTIFY),
+ DP_STR(QUERY_STREAM_ENC_STATUS),
+ };
+
+ if (req_type >= ARRAY_SIZE(req_type_str) ||
+ !req_type_str[req_type])
+ return "unknown";
+
+ return req_type_str[req_type];
+ }
+
+ #undef DP_STR
+ #define DP_STR(x) [DP_NAK_ ## x] = #x
+
+ static const char *drm_dp_mst_nak_reason_str(u8 nak_reason)
+ {
+ static const char * const nak_reason_str[] = {
+ DP_STR(WRITE_FAILURE),
+ DP_STR(INVALID_READ),
+ DP_STR(CRC_FAILURE),
+ DP_STR(BAD_PARAM),
+ DP_STR(DEFER),
+ DP_STR(LINK_FAILURE),
+ DP_STR(NO_RESOURCES),
+ DP_STR(DPCD_FAIL),
+ DP_STR(I2C_NAK),
+ DP_STR(ALLOCATE_FAIL),
+ };
+
+ if (nak_reason >= ARRAY_SIZE(nak_reason_str) ||
+ !nak_reason_str[nak_reason])
+ return "unknown";
+
+ return nak_reason_str[nak_reason];
+ }
+
+ #undef DP_STR
+ #define DP_STR(x) [DRM_DP_SIDEBAND_TX_ ## x] = #x
+
+ static const char *drm_dp_mst_sideband_tx_state_str(int state)
+ {
+ static const char * const sideband_reason_str[] = {
+ DP_STR(QUEUED),
+ DP_STR(START_SEND),
+ DP_STR(SENT),
+ DP_STR(RX),
+ DP_STR(TIMEOUT),
+ };
+
+ if (state >= ARRAY_SIZE(sideband_reason_str) ||
+ !sideband_reason_str[state])
+ return "unknown";
+
+ return sideband_reason_str[state];
+ }
+
+ static int
+ drm_dp_mst_rad_to_str(const u8 rad[8], u8 lct, char *out, size_t len)
+ {
+ int i;
+ u8 unpacked_rad[16];
+
+ for (i = 0; i < lct; i++) {
+ if (i % 2)
+ unpacked_rad[i] = rad[i / 2] >> 4;
+ else
+ unpacked_rad[i] = rad[i / 2] & BIT_MASK(4);
+ }
+
+ /* TODO: Eventually add something to printk so we can format the rad
+ * like this: 1.2.3
+ */
+ return snprintf(out, len, "%*phC", lct, unpacked_rad);
+ }
+
+ /* sideband msg handling */
+ static u8 drm_dp_msg_header_crc4(const uint8_t *data, size_t num_nibbles)
+ {
+ u8 bitmask = 0x80;
+ u8 bitshift = 7;
+ u8 array_index = 0;
+ int number_of_bits = num_nibbles * 4;
+ u8 remainder = 0;
+
+ while (number_of_bits != 0) {
+ number_of_bits--;
+ remainder <<= 1;
+ remainder |= (data[array_index] & bitmask) >> bitshift;
+ bitmask >>= 1;
+ bitshift--;
+ if (bitmask == 0) {
+ bitmask = 0x80;
+ bitshift = 7;
+ array_index++;
+ }
+ if ((remainder & 0x10) == 0x10)
+ remainder ^= 0x13;
+ }
+
+ number_of_bits = 4;
+ while (number_of_bits != 0) {
+ number_of_bits--;
+ remainder <<= 1;
+ if ((remainder & 0x10) != 0)
+ remainder ^= 0x13;
+ }
+
+ return remainder;
+ }
+
+ static u8 drm_dp_msg_data_crc4(const uint8_t *data, u8 number_of_bytes)
+ {
+ u8 bitmask = 0x80;
+ u8 bitshift = 7;
+ u8 array_index = 0;
+ int number_of_bits = number_of_bytes * 8;
+ u16 remainder = 0;
+
+ while (number_of_bits != 0) {
+ number_of_bits--;
+ remainder <<= 1;
+ remainder |= (data[array_index] & bitmask) >> bitshift;
+ bitmask >>= 1;
+ bitshift--;
+ if (bitmask == 0) {
+ bitmask = 0x80;
+ bitshift = 7;
+ array_index++;
+ }
+ if ((remainder & 0x100) == 0x100)
+ remainder ^= 0xd5;
+ }
+
+ number_of_bits = 8;
+ while (number_of_bits != 0) {
+ number_of_bits--;
+ remainder <<= 1;
+ if ((remainder & 0x100) != 0)
+ remainder ^= 0xd5;
+ }
+
+ return remainder & 0xff;
+ }
+ static inline u8 drm_dp_calc_sb_hdr_size(struct drm_dp_sideband_msg_hdr *hdr)
+ {
+ u8 size = 3;
+
+ size += (hdr->lct / 2);
+ return size;
+ }
+
+ static void drm_dp_encode_sideband_msg_hdr(struct drm_dp_sideband_msg_hdr *hdr,
+ u8 *buf, int *len)
+ {
+ int idx = 0;
+ int i;
+ u8 crc4;
+
+ buf[idx++] = ((hdr->lct & 0xf) << 4) | (hdr->lcr & 0xf);
+ for (i = 0; i < (hdr->lct / 2); i++)
+ buf[idx++] = hdr->rad[i];
+ buf[idx++] = (hdr->broadcast << 7) | (hdr->path_msg << 6) |
+ (hdr->msg_len & 0x3f);
+ buf[idx++] = (hdr->somt << 7) | (hdr->eomt << 6) | (hdr->seqno << 4);
+
+ crc4 = drm_dp_msg_header_crc4(buf, (idx * 2) - 1);
+ buf[idx - 1] |= (crc4 & 0xf);
+
+ *len = idx;
+ }
+
+ static bool drm_dp_decode_sideband_msg_hdr(const struct drm_dp_mst_topology_mgr *mgr,
+ struct drm_dp_sideband_msg_hdr *hdr,
+ u8 *buf, int buflen, u8 *hdrlen)
+ {
+ u8 crc4;
+ u8 len;
+ int i;
+ u8 idx;
+
+ if (buf[0] == 0)
+ return false;
+ len = 3;
+ len += ((buf[0] & 0xf0) >> 4) / 2;
+ if (len > buflen)
+ return false;
+ crc4 = drm_dp_msg_header_crc4(buf, (len * 2) - 1);
+
+ if ((crc4 & 0xf) != (buf[len - 1] & 0xf)) {
+ drm_dbg_kms(mgr->dev, "crc4 mismatch 0x%x 0x%x\n", crc4, buf[len - 1]);
+ return false;
+ }
+
+ hdr->lct = (buf[0] & 0xf0) >> 4;
+ hdr->lcr = (buf[0] & 0xf);
+ idx = 1;
+ for (i = 0; i < (hdr->lct / 2); i++)
+ hdr->rad[i] = buf[idx++];
+ hdr->broadcast = (buf[idx] >> 7) & 0x1;
+ hdr->path_msg = (buf[idx] >> 6) & 0x1;
+ hdr->msg_len = buf[idx] & 0x3f;
+ idx++;
+ hdr->somt = (buf[idx] >> 7) & 0x1;
+ hdr->eomt = (buf[idx] >> 6) & 0x1;
+ hdr->seqno = (buf[idx] >> 4) & 0x1;
+ idx++;
+ *hdrlen = idx;
+ return true;
+ }
+
+ void
+ drm_dp_encode_sideband_req(const struct drm_dp_sideband_msg_req_body *req,
+ struct drm_dp_sideband_msg_tx *raw)
+ {
+ int idx = 0;
+ int i;
+ u8 *buf = raw->msg;
+
+ buf[idx++] = req->req_type & 0x7f;
+
+ switch (req->req_type) {
+ case DP_ENUM_PATH_RESOURCES:
+ case DP_POWER_DOWN_PHY:
+ case DP_POWER_UP_PHY:
+ buf[idx] = (req->u.port_num.port_number & 0xf) << 4;
+ idx++;
+ break;
+ case DP_ALLOCATE_PAYLOAD:
+ buf[idx] = (req->u.allocate_payload.port_number & 0xf) << 4 |
+ (req->u.allocate_payload.number_sdp_streams & 0xf);
+ idx++;
+ buf[idx] = (req->u.allocate_payload.vcpi & 0x7f);
+ idx++;
+ buf[idx] = (req->u.allocate_payload.pbn >> 8);
+ idx++;
+ buf[idx] = (req->u.allocate_payload.pbn & 0xff);
+ idx++;
+ for (i = 0; i < req->u.allocate_payload.number_sdp_streams / 2; i++) {
+ buf[idx] = ((req->u.allocate_payload.sdp_stream_sink[i * 2] & 0xf) << 4) |
+ (req->u.allocate_payload.sdp_stream_sink[i * 2 + 1] & 0xf);
+ idx++;
+ }
+ if (req->u.allocate_payload.number_sdp_streams & 1) {
+ i = req->u.allocate_payload.number_sdp_streams - 1;
+ buf[idx] = (req->u.allocate_payload.sdp_stream_sink[i] & 0xf) << 4;
+ idx++;
+ }
+ break;
+ case DP_QUERY_PAYLOAD:
+ buf[idx] = (req->u.query_payload.port_number & 0xf) << 4;
+ idx++;
+ buf[idx] = (req->u.query_payload.vcpi & 0x7f);
+ idx++;
+ break;
+ case DP_REMOTE_DPCD_READ:
+ buf[idx] = (req->u.dpcd_read.port_number & 0xf) << 4;
+ buf[idx] |= ((req->u.dpcd_read.dpcd_address & 0xf0000) >> 16) & 0xf;
+ idx++;
+ buf[idx] = (req->u.dpcd_read.dpcd_address & 0xff00) >> 8;
+ idx++;
+ buf[idx] = (req->u.dpcd_read.dpcd_address & 0xff);
+ idx++;
+ buf[idx] = (req->u.dpcd_read.num_bytes);
+ idx++;
+ break;
+
+ case DP_REMOTE_DPCD_WRITE:
+ buf[idx] = (req->u.dpcd_write.port_number & 0xf) << 4;
+ buf[idx] |= ((req->u.dpcd_write.dpcd_address & 0xf0000) >> 16) & 0xf;
+ idx++;
+ buf[idx] = (req->u.dpcd_write.dpcd_address & 0xff00) >> 8;
+ idx++;
+ buf[idx] = (req->u.dpcd_write.dpcd_address & 0xff);
+ idx++;
+ buf[idx] = (req->u.dpcd_write.num_bytes);
+ idx++;
+ memcpy(&buf[idx], req->u.dpcd_write.bytes, req->u.dpcd_write.num_bytes);
+ idx += req->u.dpcd_write.num_bytes;
+ break;
+ case DP_REMOTE_I2C_READ:
+ buf[idx] = (req->u.i2c_read.port_number & 0xf) << 4;
+ buf[idx] |= (req->u.i2c_read.num_transactions & 0x3);
+ idx++;
+ for (i = 0; i < (req->u.i2c_read.num_transactions & 0x3); i++) {
+ buf[idx] = req->u.i2c_read.transactions[i].i2c_dev_id & 0x7f;
+ idx++;
+ buf[idx] = req->u.i2c_read.transactions[i].num_bytes;
+ idx++;
+ memcpy(&buf[idx], req->u.i2c_read.transactions[i].bytes, req->u.i2c_read.transactions[i].num_bytes);
+ idx += req->u.i2c_read.transactions[i].num_bytes;
+
+ buf[idx] = (req->u.i2c_read.transactions[i].no_stop_bit & 0x1) << 4;
+ buf[idx] |= (req->u.i2c_read.transactions[i].i2c_transaction_delay & 0xf);
+ idx++;
+ }
+ buf[idx] = (req->u.i2c_read.read_i2c_device_id) & 0x7f;
+ idx++;
+ buf[idx] = (req->u.i2c_read.num_bytes_read);
+ idx++;
+ break;
+
+ case DP_REMOTE_I2C_WRITE:
+ buf[idx] = (req->u.i2c_write.port_number & 0xf) << 4;
+ idx++;
+ buf[idx] = (req->u.i2c_write.write_i2c_device_id) & 0x7f;
+ idx++;
+ buf[idx] = (req->u.i2c_write.num_bytes);
+ idx++;
+ memcpy(&buf[idx], req->u.i2c_write.bytes, req->u.i2c_write.num_bytes);
+ idx += req->u.i2c_write.num_bytes;
+ break;
+ case DP_QUERY_STREAM_ENC_STATUS: {
+ const struct drm_dp_query_stream_enc_status *msg;
+
+ msg = &req->u.enc_status;
+ buf[idx] = msg->stream_id;
+ idx++;
+ memcpy(&buf[idx], msg->client_id, sizeof(msg->client_id));
+ idx += sizeof(msg->client_id);
+ buf[idx] = 0;
+ buf[idx] |= FIELD_PREP(GENMASK(1, 0), msg->stream_event);
+ buf[idx] |= msg->valid_stream_event ? BIT(2) : 0;
+ buf[idx] |= FIELD_PREP(GENMASK(4, 3), msg->stream_behavior);
+ buf[idx] |= msg->valid_stream_behavior ? BIT(5) : 0;
+ idx++;
+ }
+ break;
+ }
+ raw->cur_len = idx;
+ }
+ EXPORT_SYMBOL_FOR_TESTS_ONLY(drm_dp_encode_sideband_req);
+
+ /* Decode a sideband request we've encoded, mainly used for debugging */
+ int
+ drm_dp_decode_sideband_req(const struct drm_dp_sideband_msg_tx *raw,
+ struct drm_dp_sideband_msg_req_body *req)
+ {
+ const u8 *buf = raw->msg;
+ int i, idx = 0;
+
+ req->req_type = buf[idx++] & 0x7f;
+ switch (req->req_type) {
+ case DP_ENUM_PATH_RESOURCES:
+ case DP_POWER_DOWN_PHY:
+ case DP_POWER_UP_PHY:
+ req->u.port_num.port_number = (buf[idx] >> 4) & 0xf;
+ break;
+ case DP_ALLOCATE_PAYLOAD:
+ {
+ struct drm_dp_allocate_payload *a =
+ &req->u.allocate_payload;
+
+ a->number_sdp_streams = buf[idx] & 0xf;
+ a->port_number = (buf[idx] >> 4) & 0xf;
+
+ WARN_ON(buf[++idx] & 0x80);
+ a->vcpi = buf[idx] & 0x7f;
+
+ a->pbn = buf[++idx] << 8;
+ a->pbn |= buf[++idx];
+
+ idx++;
+ for (i = 0; i < a->number_sdp_streams; i++) {
+ a->sdp_stream_sink[i] =
+ (buf[idx + (i / 2)] >> ((i % 2) ? 0 : 4)) & 0xf;
+ }
+ }
+ break;
+ case DP_QUERY_PAYLOAD:
+ req->u.query_payload.port_number = (buf[idx] >> 4) & 0xf;
+ WARN_ON(buf[++idx] & 0x80);
+ req->u.query_payload.vcpi = buf[idx] & 0x7f;
+ break;
+ case DP_REMOTE_DPCD_READ:
+ {
+ struct drm_dp_remote_dpcd_read *r = &req->u.dpcd_read;
+
+ r->port_number = (buf[idx] >> 4) & 0xf;
+
+ r->dpcd_address = (buf[idx] << 16) & 0xf0000;
+ r->dpcd_address |= (buf[++idx] << 8) & 0xff00;
+ r->dpcd_address |= buf[++idx] & 0xff;
+
+ r->num_bytes = buf[++idx];
+ }
+ break;
+ case DP_REMOTE_DPCD_WRITE:
+ {
+ struct drm_dp_remote_dpcd_write *w =
+ &req->u.dpcd_write;
+
+ w->port_number = (buf[idx] >> 4) & 0xf;
+
+ w->dpcd_address = (buf[idx] << 16) & 0xf0000;
+ w->dpcd_address |= (buf[++idx] << 8) & 0xff00;
+ w->dpcd_address |= buf[++idx] & 0xff;
+
+ w->num_bytes = buf[++idx];
+
+ w->bytes = kmemdup(&buf[++idx], w->num_bytes,
+ GFP_KERNEL);
+ if (!w->bytes)
+ return -ENOMEM;
+ }
+ break;
+ case DP_REMOTE_I2C_READ:
+ {
+ struct drm_dp_remote_i2c_read *r = &req->u.i2c_read;
+ struct drm_dp_remote_i2c_read_tx *tx;
+ bool failed = false;
+
+ r->num_transactions = buf[idx] & 0x3;
+ r->port_number = (buf[idx] >> 4) & 0xf;
+ for (i = 0; i < r->num_transactions; i++) {
+ tx = &r->transactions[i];
+
+ tx->i2c_dev_id = buf[++idx] & 0x7f;
+ tx->num_bytes = buf[++idx];
+ tx->bytes = kmemdup(&buf[++idx],
+ tx->num_bytes,
+ GFP_KERNEL);
+ if (!tx->bytes) {
+ failed = true;
+ break;
+ }
+ idx += tx->num_bytes;
+ tx->no_stop_bit = (buf[idx] >> 5) & 0x1;
+ tx->i2c_transaction_delay = buf[idx] & 0xf;
+ }
+
+ if (failed) {
+ for (i = 0; i < r->num_transactions; i++) {
+ tx = &r->transactions[i];
+ kfree(tx->bytes);
+ }
+ return -ENOMEM;
+ }
+
+ r->read_i2c_device_id = buf[++idx] & 0x7f;
+ r->num_bytes_read = buf[++idx];
+ }
+ break;
+ case DP_REMOTE_I2C_WRITE:
+ {
+ struct drm_dp_remote_i2c_write *w = &req->u.i2c_write;
+
+ w->port_number = (buf[idx] >> 4) & 0xf;
+ w->write_i2c_device_id = buf[++idx] & 0x7f;
+ w->num_bytes = buf[++idx];
+ w->bytes = kmemdup(&buf[++idx], w->num_bytes,
+ GFP_KERNEL);
+ if (!w->bytes)
+ return -ENOMEM;
+ }
+ break;
+ case DP_QUERY_STREAM_ENC_STATUS:
+ req->u.enc_status.stream_id = buf[idx++];
+ for (i = 0; i < sizeof(req->u.enc_status.client_id); i++)
+ req->u.enc_status.client_id[i] = buf[idx++];
+
+ req->u.enc_status.stream_event = FIELD_GET(GENMASK(1, 0),
+ buf[idx]);
+ req->u.enc_status.valid_stream_event = FIELD_GET(BIT(2),
+ buf[idx]);
+ req->u.enc_status.stream_behavior = FIELD_GET(GENMASK(4, 3),
+ buf[idx]);
+ req->u.enc_status.valid_stream_behavior = FIELD_GET(BIT(5),
+ buf[idx]);
+ break;
+ }
+
+ return 0;
+ }
+ EXPORT_SYMBOL_FOR_TESTS_ONLY(drm_dp_decode_sideband_req);
+
+ void
+ drm_dp_dump_sideband_msg_req_body(const struct drm_dp_sideband_msg_req_body *req,
+ int indent, struct drm_printer *printer)
+ {
+ int i;
+
+ #define P(f, ...) drm_printf_indent(printer, indent, f, ##__VA_ARGS__)
+ if (req->req_type == DP_LINK_ADDRESS) {
+ /* No contents to print */
+ P("type=%s\n", drm_dp_mst_req_type_str(req->req_type));
+ return;
+ }
+
+ P("type=%s contents:\n", drm_dp_mst_req_type_str(req->req_type));
+ indent++;
+
+ switch (req->req_type) {
+ case DP_ENUM_PATH_RESOURCES:
+ case DP_POWER_DOWN_PHY:
+ case DP_POWER_UP_PHY:
+ P("port=%d\n", req->u.port_num.port_number);
+ break;
+ case DP_ALLOCATE_PAYLOAD:
+ P("port=%d vcpi=%d pbn=%d sdp_streams=%d %*ph\n",
+ req->u.allocate_payload.port_number,
+ req->u.allocate_payload.vcpi, req->u.allocate_payload.pbn,
+ req->u.allocate_payload.number_sdp_streams,
+ req->u.allocate_payload.number_sdp_streams,
+ req->u.allocate_payload.sdp_stream_sink);
+ break;
+ case DP_QUERY_PAYLOAD:
+ P("port=%d vcpi=%d\n",
+ req->u.query_payload.port_number,
+ req->u.query_payload.vcpi);
+ break;
+ case DP_REMOTE_DPCD_READ:
+ P("port=%d dpcd_addr=%05x len=%d\n",
+ req->u.dpcd_read.port_number, req->u.dpcd_read.dpcd_address,
+ req->u.dpcd_read.num_bytes);
+ break;
+ case DP_REMOTE_DPCD_WRITE:
+ P("port=%d addr=%05x len=%d: %*ph\n",
+ req->u.dpcd_write.port_number,
+ req->u.dpcd_write.dpcd_address,
+ req->u.dpcd_write.num_bytes, req->u.dpcd_write.num_bytes,
+ req->u.dpcd_write.bytes);
+ break;
+ case DP_REMOTE_I2C_READ:
+ P("port=%d num_tx=%d id=%d size=%d:\n",
+ req->u.i2c_read.port_number,
+ req->u.i2c_read.num_transactions,
+ req->u.i2c_read.read_i2c_device_id,
+ req->u.i2c_read.num_bytes_read);
+
+ indent++;
+ for (i = 0; i < req->u.i2c_read.num_transactions; i++) {
+ const struct drm_dp_remote_i2c_read_tx *rtx =
+ &req->u.i2c_read.transactions[i];
+
+ P("%d: id=%03d size=%03d no_stop_bit=%d tx_delay=%03d: %*ph\n",
+ i, rtx->i2c_dev_id, rtx->num_bytes,
+ rtx->no_stop_bit, rtx->i2c_transaction_delay,
+ rtx->num_bytes, rtx->bytes);
+ }
+ break;
+ case DP_REMOTE_I2C_WRITE:
+ P("port=%d id=%d size=%d: %*ph\n",
+ req->u.i2c_write.port_number,
+ req->u.i2c_write.write_i2c_device_id,
+ req->u.i2c_write.num_bytes, req->u.i2c_write.num_bytes,
+ req->u.i2c_write.bytes);
+ break;
+ case DP_QUERY_STREAM_ENC_STATUS:
+ P("stream_id=%u client_id=%*ph stream_event=%x "
+ "valid_event=%d stream_behavior=%x valid_behavior=%d",
+ req->u.enc_status.stream_id,
+ (int)ARRAY_SIZE(req->u.enc_status.client_id),
+ req->u.enc_status.client_id, req->u.enc_status.stream_event,
+ req->u.enc_status.valid_stream_event,
+ req->u.enc_status.stream_behavior,
+ req->u.enc_status.valid_stream_behavior);
+ break;
+ default:
+ P("???\n");
+ break;
+ }
+ #undef P
+ }
+ EXPORT_SYMBOL_FOR_TESTS_ONLY(drm_dp_dump_sideband_msg_req_body);
+
+ static inline void
+ drm_dp_mst_dump_sideband_msg_tx(struct drm_printer *p,
+ const struct drm_dp_sideband_msg_tx *txmsg)
+ {
+ struct drm_dp_sideband_msg_req_body req;
+ char buf[64];
+ int ret;
+ int i;
+
+ drm_dp_mst_rad_to_str(txmsg->dst->rad, txmsg->dst->lct, buf,
+ sizeof(buf));
+ drm_printf(p, "txmsg cur_offset=%x cur_len=%x seqno=%x state=%s path_msg=%d dst=%s\n",
+ txmsg->cur_offset, txmsg->cur_len, txmsg->seqno,
+ drm_dp_mst_sideband_tx_state_str(txmsg->state),
+ txmsg->path_msg, buf);
+
+ ret = drm_dp_decode_sideband_req(txmsg, &req);
+ if (ret) {
+ drm_printf(p, "<failed to decode sideband req: %d>\n", ret);
+ return;
+ }
+ drm_dp_dump_sideband_msg_req_body(&req, 1, p);
+
+ switch (req.req_type) {
+ case DP_REMOTE_DPCD_WRITE:
+ kfree(req.u.dpcd_write.bytes);
+ break;
+ case DP_REMOTE_I2C_READ:
+ for (i = 0; i < req.u.i2c_read.num_transactions; i++)
+ kfree(req.u.i2c_read.transactions[i].bytes);
+ break;
+ case DP_REMOTE_I2C_WRITE:
+ kfree(req.u.i2c_write.bytes);
+ break;
+ }
+ }
+
+ static void drm_dp_crc_sideband_chunk_req(u8 *msg, u8 len)
+ {
+ u8 crc4;
+
+ crc4 = drm_dp_msg_data_crc4(msg, len);
+ msg[len] = crc4;
+ }
+
+ static void drm_dp_encode_sideband_reply(struct drm_dp_sideband_msg_reply_body *rep,
+ struct drm_dp_sideband_msg_tx *raw)
+ {
+ int idx = 0;
+ u8 *buf = raw->msg;
+
+ buf[idx++] = (rep->reply_type & 0x1) << 7 | (rep->req_type & 0x7f);
+
+ raw->cur_len = idx;
+ }
+
+ static int drm_dp_sideband_msg_set_header(struct drm_dp_sideband_msg_rx *msg,
+ struct drm_dp_sideband_msg_hdr *hdr,
+ u8 hdrlen)
+ {
+ /*
+ * ignore out-of-order messages or messages that are part of a
+ * failed transaction
+ */
+ if (!hdr->somt && !msg->have_somt)
+ return false;
+
+ /* get length contained in this portion */
+ msg->curchunk_idx = 0;
+ msg->curchunk_len = hdr->msg_len;
+ msg->curchunk_hdrlen = hdrlen;
+
+ /* we have already gotten an somt - don't bother parsing */
+ if (hdr->somt && msg->have_somt)
+ return false;
+
+ if (hdr->somt) {
+ memcpy(&msg->initial_hdr, hdr,
+ sizeof(struct drm_dp_sideband_msg_hdr));
+ msg->have_somt = true;
+ }
+ if (hdr->eomt)
+ msg->have_eomt = true;
+
+ return true;
+ }
+
+ /* this adds a chunk of msg to the builder to get the final msg */
+ static bool drm_dp_sideband_append_payload(struct drm_dp_sideband_msg_rx *msg,
+ u8 *replybuf, u8 replybuflen)
+ {
+ u8 crc4;
+
+ memcpy(&msg->chunk[msg->curchunk_idx], replybuf, replybuflen);
+ msg->curchunk_idx += replybuflen;
+
+ if (msg->curchunk_idx >= msg->curchunk_len) {
+ /* do CRC */
+ crc4 = drm_dp_msg_data_crc4(msg->chunk, msg->curchunk_len - 1);
+ if (crc4 != msg->chunk[msg->curchunk_len - 1])
+ print_hex_dump(KERN_DEBUG, "wrong crc",
+ DUMP_PREFIX_NONE, 16, 1,
+ msg->chunk, msg->curchunk_len, false);
+ /* copy chunk into bigger msg */
+ memcpy(&msg->msg[msg->curlen], msg->chunk, msg->curchunk_len - 1);
+ msg->curlen += msg->curchunk_len - 1;
+ }
+ return true;
+ }
+
+ static bool drm_dp_sideband_parse_link_address(const struct drm_dp_mst_topology_mgr *mgr,
+ struct drm_dp_sideband_msg_rx *raw,
+ struct drm_dp_sideband_msg_reply_body *repmsg)
+ {
+ int idx = 1;
+ int i;
+
+ memcpy(repmsg->u.link_addr.guid, &raw->msg[idx], 16);
+ idx += 16;
+ repmsg->u.link_addr.nports = raw->msg[idx] & 0xf;
+ idx++;
+ if (idx > raw->curlen)
+ goto fail_len;
+ for (i = 0; i < repmsg->u.link_addr.nports; i++) {
+ if (raw->msg[idx] & 0x80)
+ repmsg->u.link_addr.ports[i].input_port = 1;
+
+ repmsg->u.link_addr.ports[i].peer_device_type = (raw->msg[idx] >> 4) & 0x7;
+ repmsg->u.link_addr.ports[i].port_number = (raw->msg[idx] & 0xf);
+
+ idx++;
+ if (idx > raw->curlen)
+ goto fail_len;
+ repmsg->u.link_addr.ports[i].mcs = (raw->msg[idx] >> 7) & 0x1;
+ repmsg->u.link_addr.ports[i].ddps = (raw->msg[idx] >> 6) & 0x1;
+ if (repmsg->u.link_addr.ports[i].input_port == 0)
+ repmsg->u.link_addr.ports[i].legacy_device_plug_status = (raw->msg[idx] >> 5) & 0x1;
+ idx++;
+ if (idx > raw->curlen)
+ goto fail_len;
+ if (repmsg->u.link_addr.ports[i].input_port == 0) {
+ repmsg->u.link_addr.ports[i].dpcd_revision = (raw->msg[idx]);
+ idx++;
+ if (idx > raw->curlen)
+ goto fail_len;
+ memcpy(repmsg->u.link_addr.ports[i].peer_guid, &raw->msg[idx], 16);
+ idx += 16;
+ if (idx > raw->curlen)
+ goto fail_len;
+ repmsg->u.link_addr.ports[i].num_sdp_streams = (raw->msg[idx] >> 4) & 0xf;
+ repmsg->u.link_addr.ports[i].num_sdp_stream_sinks = (raw->msg[idx] & 0xf);
+ idx++;
+
+ }
+ if (idx > raw->curlen)
+ goto fail_len;
+ }
+
+ return true;
+ fail_len:
+ DRM_DEBUG_KMS("link address reply parse length fail %d %d\n", idx, raw->curlen);
+ return false;
+ }
+
+ static bool drm_dp_sideband_parse_remote_dpcd_read(struct drm_dp_sideband_msg_rx *raw,
+ struct drm_dp_sideband_msg_reply_body *repmsg)
+ {
+ int idx = 1;
+
+ repmsg->u.remote_dpcd_read_ack.port_number = raw->msg[idx] & 0xf;
+ idx++;
+ if (idx > raw->curlen)
+ goto fail_len;
+ repmsg->u.remote_dpcd_read_ack.num_bytes = raw->msg[idx];
+ idx++;
+ if (idx > raw->curlen)
+ goto fail_len;
+
+ memcpy(repmsg->u.remote_dpcd_read_ack.bytes, &raw->msg[idx], repmsg->u.remote_dpcd_read_ack.num_bytes);
+ return true;
+ fail_len:
+ DRM_DEBUG_KMS("link address reply parse length fail %d %d\n", idx, raw->curlen);
+ return false;
+ }
+
+ static bool drm_dp_sideband_parse_remote_dpcd_write(struct drm_dp_sideband_msg_rx *raw,
+ struct drm_dp_sideband_msg_reply_body *repmsg)
+ {
+ int idx = 1;
+
+ repmsg->u.remote_dpcd_write_ack.port_number = raw->msg[idx] & 0xf;
+ idx++;
+ if (idx > raw->curlen)
+ goto fail_len;
+ return true;
+ fail_len:
+ DRM_DEBUG_KMS("parse length fail %d %d\n", idx, raw->curlen);
+ return false;
+ }
+
+ static bool drm_dp_sideband_parse_remote_i2c_read_ack(struct drm_dp_sideband_msg_rx *raw,
+ struct drm_dp_sideband_msg_reply_body *repmsg)
+ {
+ int idx = 1;
+
+ repmsg->u.remote_i2c_read_ack.port_number = (raw->msg[idx] & 0xf);
+ idx++;
+ if (idx > raw->curlen)
+ goto fail_len;
+ repmsg->u.remote_i2c_read_ack.num_bytes = raw->msg[idx];
+ idx++;
+ /* TODO check */
+ memcpy(repmsg->u.remote_i2c_read_ack.bytes, &raw->msg[idx], repmsg->u.remote_i2c_read_ack.num_bytes);
+ return true;
+ fail_len:
+ DRM_DEBUG_KMS("remote i2c reply parse length fail %d %d\n", idx, raw->curlen);
+ return false;
+ }
+
+ static bool drm_dp_sideband_parse_enum_path_resources_ack(struct drm_dp_sideband_msg_rx *raw,
+ struct drm_dp_sideband_msg_reply_body *repmsg)
+ {
+ int idx = 1;
+
+ repmsg->u.path_resources.port_number = (raw->msg[idx] >> 4) & 0xf;
+ repmsg->u.path_resources.fec_capable = raw->msg[idx] & 0x1;
+ idx++;
+ if (idx > raw->curlen)
+ goto fail_len;
+ repmsg->u.path_resources.full_payload_bw_number = (raw->msg[idx] << 8) | (raw->msg[idx+1]);
+ idx += 2;
+ if (idx > raw->curlen)
+ goto fail_len;
+ repmsg->u.path_resources.avail_payload_bw_number = (raw->msg[idx] << 8) | (raw->msg[idx+1]);
+ idx += 2;
+ if (idx > raw->curlen)
+ goto fail_len;
+ return true;
+ fail_len:
+ DRM_DEBUG_KMS("enum resource parse length fail %d %d\n", idx, raw->curlen);
+ return false;
+ }
+
+ static bool drm_dp_sideband_parse_allocate_payload_ack(struct drm_dp_sideband_msg_rx *raw,
+ struct drm_dp_sideband_msg_reply_body *repmsg)
+ {
+ int idx = 1;
+
+ repmsg->u.allocate_payload.port_number = (raw->msg[idx] >> 4) & 0xf;
+ idx++;
+ if (idx > raw->curlen)
+ goto fail_len;
+ repmsg->u.allocate_payload.vcpi = raw->msg[idx];
+ idx++;
+ if (idx > raw->curlen)
+ goto fail_len;
+ repmsg->u.allocate_payload.allocated_pbn = (raw->msg[idx] << 8) | (raw->msg[idx+1]);
+ idx += 2;
+ if (idx > raw->curlen)
+ goto fail_len;
+ return true;
+ fail_len:
+ DRM_DEBUG_KMS("allocate payload parse length fail %d %d\n", idx, raw->curlen);
+ return false;
+ }
+
+ static bool drm_dp_sideband_parse_query_payload_ack(struct drm_dp_sideband_msg_rx *raw,
+ struct drm_dp_sideband_msg_reply_body *repmsg)
+ {
+ int idx = 1;
+
+ repmsg->u.query_payload.port_number = (raw->msg[idx] >> 4) & 0xf;
+ idx++;
+ if (idx > raw->curlen)
+ goto fail_len;
+ repmsg->u.query_payload.allocated_pbn = (raw->msg[idx] << 8) | (raw->msg[idx + 1]);
+ idx += 2;
+ if (idx > raw->curlen)
+ goto fail_len;
+ return true;
+ fail_len:
+ DRM_DEBUG_KMS("query payload parse length fail %d %d\n", idx, raw->curlen);
+ return false;
+ }
+
+ static bool drm_dp_sideband_parse_power_updown_phy_ack(struct drm_dp_sideband_msg_rx *raw,
+ struct drm_dp_sideband_msg_reply_body *repmsg)
+ {
+ int idx = 1;
+
+ repmsg->u.port_number.port_number = (raw->msg[idx] >> 4) & 0xf;
+ idx++;
+ if (idx > raw->curlen) {
+ DRM_DEBUG_KMS("power up/down phy parse length fail %d %d\n",
+ idx, raw->curlen);
+ return false;
+ }
+ return true;
+ }
+
+ static bool
+ drm_dp_sideband_parse_query_stream_enc_status(
+ struct drm_dp_sideband_msg_rx *raw,
+ struct drm_dp_sideband_msg_reply_body *repmsg)
+ {
+ struct drm_dp_query_stream_enc_status_ack_reply *reply;
+
+ reply = &repmsg->u.enc_status;
+
+ reply->stream_id = raw->msg[3];
+
+ reply->reply_signed = raw->msg[2] & BIT(0);
+
+ /*
+ * NOTE: It's my impression from reading the spec that the below parsing
+ * is correct. However I noticed while testing with an HDCP 1.4 display
+ * through an HDCP 2.2 hub that only bit 3 was set. In that case, I
+ * would expect both bits to be set. So keep the parsing following the
+ * spec, but beware reality might not match the spec (at least for some
+ * configurations).
+ */
+ reply->hdcp_1x_device_present = raw->msg[2] & BIT(4);
+ reply->hdcp_2x_device_present = raw->msg[2] & BIT(3);
+
+ reply->query_capable_device_present = raw->msg[2] & BIT(5);
+ reply->legacy_device_present = raw->msg[2] & BIT(6);
+ reply->unauthorizable_device_present = raw->msg[2] & BIT(7);
+
+ reply->auth_completed = !!(raw->msg[1] & BIT(3));
+ reply->encryption_enabled = !!(raw->msg[1] & BIT(4));
+ reply->repeater_present = !!(raw->msg[1] & BIT(5));
+ reply->state = (raw->msg[1] & GENMASK(7, 6)) >> 6;
+
+ return true;
+ }
+
+ static bool drm_dp_sideband_parse_reply(const struct drm_dp_mst_topology_mgr *mgr,
+ struct drm_dp_sideband_msg_rx *raw,
+ struct drm_dp_sideband_msg_reply_body *msg)
+ {
+ memset(msg, 0, sizeof(*msg));
+ msg->reply_type = (raw->msg[0] & 0x80) >> 7;
+ msg->req_type = (raw->msg[0] & 0x7f);
+
+ if (msg->reply_type == DP_SIDEBAND_REPLY_NAK) {
+ memcpy(msg->u.nak.guid, &raw->msg[1], 16);
+ msg->u.nak.reason = raw->msg[17];
+ msg->u.nak.nak_data = raw->msg[18];
+ return false;
+ }
+
+ switch (msg->req_type) {
+ case DP_LINK_ADDRESS:
+ return drm_dp_sideband_parse_link_address(mgr, raw, msg);
+ case DP_QUERY_PAYLOAD:
+ return drm_dp_sideband_parse_query_payload_ack(raw, msg);
+ case DP_REMOTE_DPCD_READ:
+ return drm_dp_sideband_parse_remote_dpcd_read(raw, msg);
+ case DP_REMOTE_DPCD_WRITE:
+ return drm_dp_sideband_parse_remote_dpcd_write(raw, msg);
+ case DP_REMOTE_I2C_READ:
+ return drm_dp_sideband_parse_remote_i2c_read_ack(raw, msg);
+ case DP_REMOTE_I2C_WRITE:
+ return true; /* since there's nothing to parse */
+ case DP_ENUM_PATH_RESOURCES:
+ return drm_dp_sideband_parse_enum_path_resources_ack(raw, msg);
+ case DP_ALLOCATE_PAYLOAD:
+ return drm_dp_sideband_parse_allocate_payload_ack(raw, msg);
+ case DP_POWER_DOWN_PHY:
+ case DP_POWER_UP_PHY:
+ return drm_dp_sideband_parse_power_updown_phy_ack(raw, msg);
+ case DP_CLEAR_PAYLOAD_ID_TABLE:
+ return true; /* since there's nothing to parse */
+ case DP_QUERY_STREAM_ENC_STATUS:
+ return drm_dp_sideband_parse_query_stream_enc_status(raw, msg);
+ default:
+ drm_err(mgr->dev, "Got unknown reply 0x%02x (%s)\n",
+ msg->req_type, drm_dp_mst_req_type_str(msg->req_type));
+ return false;
+ }
+ }
+
+ static bool
+ drm_dp_sideband_parse_connection_status_notify(const struct drm_dp_mst_topology_mgr *mgr,
+ struct drm_dp_sideband_msg_rx *raw,
+ struct drm_dp_sideband_msg_req_body *msg)
+ {
+ int idx = 1;
+
+ msg->u.conn_stat.port_number = (raw->msg[idx] & 0xf0) >> 4;
+ idx++;
+ if (idx > raw->curlen)
+ goto fail_len;
+
+ memcpy(msg->u.conn_stat.guid, &raw->msg[idx], 16);
+ idx += 16;
+ if (idx > raw->curlen)
+ goto fail_len;
+
+ msg->u.conn_stat.legacy_device_plug_status = (raw->msg[idx] >> 6) & 0x1;
+ msg->u.conn_stat.displayport_device_plug_status = (raw->msg[idx] >> 5) & 0x1;
+ msg->u.conn_stat.message_capability_status = (raw->msg[idx] >> 4) & 0x1;
+ msg->u.conn_stat.input_port = (raw->msg[idx] >> 3) & 0x1;
+ msg->u.conn_stat.peer_device_type = (raw->msg[idx] & 0x7);
+ idx++;
+ return true;
+ fail_len:
+ drm_dbg_kms(mgr->dev, "connection status reply parse length fail %d %d\n",
+ idx, raw->curlen);
+ return false;
+ }
+
+ static bool drm_dp_sideband_parse_resource_status_notify(const struct drm_dp_mst_topology_mgr *mgr,
+ struct drm_dp_sideband_msg_rx *raw,
+ struct drm_dp_sideband_msg_req_body *msg)
+ {
+ int idx = 1;
+
+ msg->u.resource_stat.port_number = (raw->msg[idx] & 0xf0) >> 4;
+ idx++;
+ if (idx > raw->curlen)
+ goto fail_len;
+
+ memcpy(msg->u.resource_stat.guid, &raw->msg[idx], 16);
+ idx += 16;
+ if (idx > raw->curlen)
+ goto fail_len;
+
+ msg->u.resource_stat.available_pbn = (raw->msg[idx] << 8) | (raw->msg[idx + 1]);
+ idx++;
+ return true;
+ fail_len:
+ drm_dbg_kms(mgr->dev, "resource status reply parse length fail %d %d\n", idx, raw->curlen);
+ return false;
+ }
+
+ static bool drm_dp_sideband_parse_req(const struct drm_dp_mst_topology_mgr *mgr,
+ struct drm_dp_sideband_msg_rx *raw,
+ struct drm_dp_sideband_msg_req_body *msg)
+ {
+ memset(msg, 0, sizeof(*msg));
+ msg->req_type = (raw->msg[0] & 0x7f);
+
+ switch (msg->req_type) {
+ case DP_CONNECTION_STATUS_NOTIFY:
+ return drm_dp_sideband_parse_connection_status_notify(mgr, raw, msg);
+ case DP_RESOURCE_STATUS_NOTIFY:
+ return drm_dp_sideband_parse_resource_status_notify(mgr, raw, msg);
+ default:
+ drm_err(mgr->dev, "Got unknown request 0x%02x (%s)\n",
+ msg->req_type, drm_dp_mst_req_type_str(msg->req_type));
+ return false;
+ }
+ }
+
+ static void build_dpcd_write(struct drm_dp_sideband_msg_tx *msg,
+ u8 port_num, u32 offset, u8 num_bytes, u8 *bytes)
+ {
+ struct drm_dp_sideband_msg_req_body req;
+
+ req.req_type = DP_REMOTE_DPCD_WRITE;
+ req.u.dpcd_write.port_number = port_num;
+ req.u.dpcd_write.dpcd_address = offset;
+ req.u.dpcd_write.num_bytes = num_bytes;
+ req.u.dpcd_write.bytes = bytes;
+ drm_dp_encode_sideband_req(&req, msg);
+ }
+
+ static void build_link_address(struct drm_dp_sideband_msg_tx *msg)
+ {
+ struct drm_dp_sideband_msg_req_body req;
+
+ req.req_type = DP_LINK_ADDRESS;
+ drm_dp_encode_sideband_req(&req, msg);
+ }
+
+ static void build_clear_payload_id_table(struct drm_dp_sideband_msg_tx *msg)
+ {
+ struct drm_dp_sideband_msg_req_body req;
+
+ req.req_type = DP_CLEAR_PAYLOAD_ID_TABLE;
+ drm_dp_encode_sideband_req(&req, msg);
+ msg->path_msg = true;
+ }
+
+ static int build_enum_path_resources(struct drm_dp_sideband_msg_tx *msg,
+ int port_num)
+ {
+ struct drm_dp_sideband_msg_req_body req;
+
+ req.req_type = DP_ENUM_PATH_RESOURCES;
+ req.u.port_num.port_number = port_num;
+ drm_dp_encode_sideband_req(&req, msg);
+ msg->path_msg = true;
+ return 0;
+ }
+
+ static void build_allocate_payload(struct drm_dp_sideband_msg_tx *msg,
+ int port_num,
+ u8 vcpi, uint16_t pbn,
+ u8 number_sdp_streams,
+ u8 *sdp_stream_sink)
+ {
+ struct drm_dp_sideband_msg_req_body req;
+
+ memset(&req, 0, sizeof(req));
+ req.req_type = DP_ALLOCATE_PAYLOAD;
+ req.u.allocate_payload.port_number = port_num;
+ req.u.allocate_payload.vcpi = vcpi;
+ req.u.allocate_payload.pbn = pbn;
+ req.u.allocate_payload.number_sdp_streams = number_sdp_streams;
+ memcpy(req.u.allocate_payload.sdp_stream_sink, sdp_stream_sink,
+ number_sdp_streams);
+ drm_dp_encode_sideband_req(&req, msg);
+ msg->path_msg = true;
+ }
+
+ static void build_power_updown_phy(struct drm_dp_sideband_msg_tx *msg,
+ int port_num, bool power_up)
+ {
+ struct drm_dp_sideband_msg_req_body req;
+
+ if (power_up)
+ req.req_type = DP_POWER_UP_PHY;
+ else
+ req.req_type = DP_POWER_DOWN_PHY;
+
+ req.u.port_num.port_number = port_num;
+ drm_dp_encode_sideband_req(&req, msg);
+ msg->path_msg = true;
+ }
+
+ static int
+ build_query_stream_enc_status(struct drm_dp_sideband_msg_tx *msg, u8 stream_id,
+ u8 *q_id)
+ {
+ struct drm_dp_sideband_msg_req_body req;
+
+ req.req_type = DP_QUERY_STREAM_ENC_STATUS;
+ req.u.enc_status.stream_id = stream_id;
+ memcpy(req.u.enc_status.client_id, q_id,
+ sizeof(req.u.enc_status.client_id));
+ req.u.enc_status.stream_event = 0;
+ req.u.enc_status.valid_stream_event = false;
+ req.u.enc_status.stream_behavior = 0;
+ req.u.enc_status.valid_stream_behavior = false;
+
+ drm_dp_encode_sideband_req(&req, msg);
+ return 0;
+ }
+
+ static int drm_dp_mst_assign_payload_id(struct drm_dp_mst_topology_mgr *mgr,
+ struct drm_dp_vcpi *vcpi)
+ {
+ int ret, vcpi_ret;
+
+ mutex_lock(&mgr->payload_lock);
+ ret = find_first_zero_bit(&mgr->payload_mask, mgr->max_payloads + 1);
+ if (ret > mgr->max_payloads) {
+ ret = -EINVAL;
+ drm_dbg_kms(mgr->dev, "out of payload ids %d\n", ret);
+ goto out_unlock;
+ }
+
+ vcpi_ret = find_first_zero_bit(&mgr->vcpi_mask, mgr->max_payloads + 1);
+ if (vcpi_ret > mgr->max_payloads) {
+ ret = -EINVAL;
+ drm_dbg_kms(mgr->dev, "out of vcpi ids %d\n", ret);
+ goto out_unlock;
+ }
+
+ set_bit(ret, &mgr->payload_mask);
+ set_bit(vcpi_ret, &mgr->vcpi_mask);
+ vcpi->vcpi = vcpi_ret + 1;
+ mgr->proposed_vcpis[ret - 1] = vcpi;
+ out_unlock:
+ mutex_unlock(&mgr->payload_lock);
+ return ret;
+ }
+
+ static void drm_dp_mst_put_payload_id(struct drm_dp_mst_topology_mgr *mgr,
+ int vcpi)
+ {
+ int i;
+
+ if (vcpi == 0)
+ return;
+
+ mutex_lock(&mgr->payload_lock);
+ drm_dbg_kms(mgr->dev, "putting payload %d\n", vcpi);
+ clear_bit(vcpi - 1, &mgr->vcpi_mask);
+
+ for (i = 0; i < mgr->max_payloads; i++) {
+ if (mgr->proposed_vcpis[i] &&
+ mgr->proposed_vcpis[i]->vcpi == vcpi) {
+ mgr->proposed_vcpis[i] = NULL;
+ clear_bit(i + 1, &mgr->payload_mask);
+ }
+ }
+ mutex_unlock(&mgr->payload_lock);
+ }
+
+ static bool check_txmsg_state(struct drm_dp_mst_topology_mgr *mgr,
+ struct drm_dp_sideband_msg_tx *txmsg)
+ {
+ unsigned int state;
+
+ /*
+ * All updates to txmsg->state are protected by mgr->qlock, and the two
+ * cases we check here are terminal states. For those the barriers
+ * provided by the wake_up/wait_event pair are enough.
+ */
+ state = READ_ONCE(txmsg->state);
+ return (state == DRM_DP_SIDEBAND_TX_RX ||
+ state == DRM_DP_SIDEBAND_TX_TIMEOUT);
+ }
+
+ static int drm_dp_mst_wait_tx_reply(struct drm_dp_mst_branch *mstb,
+ struct drm_dp_sideband_msg_tx *txmsg)
+ {
+ struct drm_dp_mst_topology_mgr *mgr = mstb->mgr;
+ unsigned long wait_timeout = msecs_to_jiffies(4000);
+ unsigned long wait_expires = jiffies + wait_timeout;
+ int ret;
+
+ for (;;) {
+ /*
+ * If the driver provides a way for this, change to
+ * poll-waiting for the MST reply interrupt if we didn't receive
+ * it for 50 msec. This would cater for cases where the HPD
+ * pulse signal got lost somewhere, even though the sink raised
+ * the corresponding MST interrupt correctly. One example is the
+ * Club 3D CAC-1557 TypeC -> DP adapter which for some reason
+ * filters out short pulses with a duration less than ~540 usec.
+ *
+ * The poll period is 50 msec to avoid missing an interrupt
+ * after the sink has cleared it (after a 110msec timeout
+ * since it raised the interrupt).
+ */
+ ret = wait_event_timeout(mgr->tx_waitq,
+ check_txmsg_state(mgr, txmsg),
+ mgr->cbs->poll_hpd_irq ?
+ msecs_to_jiffies(50) :
+ wait_timeout);
+
+ if (ret || !mgr->cbs->poll_hpd_irq ||
+ time_after(jiffies, wait_expires))
+ break;
+
+ mgr->cbs->poll_hpd_irq(mgr);
+ }
+
+ mutex_lock(&mgr->qlock);
+ if (ret > 0) {
+ if (txmsg->state == DRM_DP_SIDEBAND_TX_TIMEOUT) {
+ ret = -EIO;
+ goto out;
+ }
+ } else {
+ drm_dbg_kms(mgr->dev, "timedout msg send %p %d %d\n",
+ txmsg, txmsg->state, txmsg->seqno);
+
+ /* dump some state */
+ ret = -EIO;
+
+ /* remove from q */
+ if (txmsg->state == DRM_DP_SIDEBAND_TX_QUEUED ||
+ txmsg->state == DRM_DP_SIDEBAND_TX_START_SEND ||
+ txmsg->state == DRM_DP_SIDEBAND_TX_SENT)
+ list_del(&txmsg->next);
+ }
+ out:
+ if (unlikely(ret == -EIO) && drm_debug_enabled(DRM_UT_DP)) {
+ struct drm_printer p = drm_debug_printer(DBG_PREFIX);
+
+ drm_dp_mst_dump_sideband_msg_tx(&p, txmsg);
+ }
+ mutex_unlock(&mgr->qlock);
+
+ drm_dp_mst_kick_tx(mgr);
+ return ret;
+ }
+
+ static struct drm_dp_mst_branch *drm_dp_add_mst_branch_device(u8 lct, u8 *rad)
+ {
+ struct drm_dp_mst_branch *mstb;
+
+ mstb = kzalloc(sizeof(*mstb), GFP_KERNEL);
+ if (!mstb)
+ return NULL;
+
+ mstb->lct = lct;
+ if (lct > 1)
+ memcpy(mstb->rad, rad, lct / 2);
+ INIT_LIST_HEAD(&mstb->ports);
+ kref_init(&mstb->topology_kref);
+ kref_init(&mstb->malloc_kref);
+ return mstb;
+ }
+
+ static void drm_dp_free_mst_branch_device(struct kref *kref)
+ {
+ struct drm_dp_mst_branch *mstb =
+ container_of(kref, struct drm_dp_mst_branch, malloc_kref);
+
+ if (mstb->port_parent)
+ drm_dp_mst_put_port_malloc(mstb->port_parent);
+
+ kfree(mstb);
+ }
+
+ /**
+ * DOC: Branch device and port refcounting
+ *
+ * Topology refcount overview
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * The refcounting schemes for &struct drm_dp_mst_branch and &struct
+ * drm_dp_mst_port are somewhat unusual. Both ports and branch devices have
+ * two different kinds of refcounts: topology refcounts, and malloc refcounts.
+ *
+ * Topology refcounts are not exposed to drivers, and are handled internally
+ * by the DP MST helpers. The helpers use them in order to prevent the
+ * in-memory topology state from being changed in the middle of critical
+ * operations like changing the internal state of payload allocations. This
+ * means each branch and port will be considered to be connected to the rest
+ * of the topology until its topology refcount reaches zero. Additionally,
+ * for ports this means that their associated &struct drm_connector will stay
+ * registered with userspace until the port's refcount reaches 0.
+ *
+ * Malloc refcount overview
+ * ~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * Malloc references are used to keep a &struct drm_dp_mst_port or &struct
+ * drm_dp_mst_branch allocated even after all of its topology references have
+ * been dropped, so that the driver or MST helpers can safely access each
+ * branch's last known state before it was disconnected from the topology.
+ * When the malloc refcount of a port or branch reaches 0, the memory
+ * allocation containing the &struct drm_dp_mst_branch or &struct
+ * drm_dp_mst_port respectively will be freed.
+ *
+ * For &struct drm_dp_mst_branch, malloc refcounts are not currently exposed
+ * to drivers. As of writing this documentation, there are no drivers that
+ * have a usecase for accessing &struct drm_dp_mst_branch outside of the MST
+ * helpers. Exposing this API to drivers in a race-free manner would take more
+ * tweaking of the refcounting scheme, however patches are welcome provided
+ * there is a legitimate driver usecase for this.
+ *
+ * Refcount relationships in a topology
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * Let's take a look at why the relationship between topology and malloc
+ * refcounts is designed the way it is.
+ *
+ * .. kernel-figure:: dp-mst/topology-figure-1.dot
+ *
+ * An example of topology and malloc refs in a DP MST topology with two
+ * active payloads. Topology refcount increments are indicated by solid
+ * lines, and malloc refcount increments are indicated by dashed lines.
+ * Each starts from the branch which incremented the refcount, and ends at
+ * the branch to which the refcount belongs to, i.e. the arrow points the
+ * same way as the C pointers used to reference a structure.
+ *
+ * As you can see in the above figure, every branch increments the topology
+ * refcount of its children, and increments the malloc refcount of its
+ * parent. Additionally, every payload increments the malloc refcount of its
+ * assigned port by 1.
+ *
+ * So, what would happen if MSTB #3 from the above figure was unplugged from
+ * the system, but the driver hadn't yet removed payload #2 from port #3? The
+ * topology would start to look like the figure below.
+ *
+ * .. kernel-figure:: dp-mst/topology-figure-2.dot
+ *
+ * Ports and branch devices which have been released from memory are
+ * colored grey, and references which have been removed are colored red.
+ *
+ * Whenever a port or branch device's topology refcount reaches zero, it will
+ * decrement the topology refcounts of all its children, the malloc refcount
+ * of its parent, and finally its own malloc refcount. For MSTB #4 and port
+ * #4, this means they both have been disconnected from the topology and freed
+ * from memory. But, because payload #2 is still holding a reference to port
+ * #3, port #3 is removed from the topology but its &struct drm_dp_mst_port
+ * is still accessible from memory. This also means port #3 has not yet
+ * decremented the malloc refcount of MSTB #3, so its &struct
+ * drm_dp_mst_branch will also stay allocated in memory until port #3's
+ * malloc refcount reaches 0.
+ *
+ * This relationship is necessary because in order to release payload #2, we
+ * need to be able to figure out the last relative of port #3 that's still
+ * connected to the topology. In this case, we would travel up the topology as
+ * shown below.
+ *
+ * .. kernel-figure:: dp-mst/topology-figure-3.dot
+ *
+ * And finally, remove payload #2 by communicating with port #2 through
+ * sideband transactions.
+ */
+
+ /**
+ * drm_dp_mst_get_mstb_malloc() - Increment the malloc refcount of a branch
+ * device
+ * @mstb: The &struct drm_dp_mst_branch to increment the malloc refcount of
+ *
+ * Increments &drm_dp_mst_branch.malloc_kref. When
+ * &drm_dp_mst_branch.malloc_kref reaches 0, the memory allocation for @mstb
+ * will be released and @mstb may no longer be used.
+ *
+ * See also: drm_dp_mst_put_mstb_malloc()
+ */
+ static void
+ drm_dp_mst_get_mstb_malloc(struct drm_dp_mst_branch *mstb)
+ {
+ kref_get(&mstb->malloc_kref);
+ drm_dbg(mstb->mgr->dev, "mstb %p (%d)\n", mstb, kref_read(&mstb->malloc_kref));
+ }
+
+ /**
+ * drm_dp_mst_put_mstb_malloc() - Decrement the malloc refcount of a branch
+ * device
+ * @mstb: The &struct drm_dp_mst_branch to decrement the malloc refcount of
+ *
+ * Decrements &drm_dp_mst_branch.malloc_kref. When
+ * &drm_dp_mst_branch.malloc_kref reaches 0, the memory allocation for @mstb
+ * will be released and @mstb may no longer be used.
+ *
+ * See also: drm_dp_mst_get_mstb_malloc()
+ */
+ static void
+ drm_dp_mst_put_mstb_malloc(struct drm_dp_mst_branch *mstb)
+ {
+ drm_dbg(mstb->mgr->dev, "mstb %p (%d)\n", mstb, kref_read(&mstb->malloc_kref) - 1);
+ kref_put(&mstb->malloc_kref, drm_dp_free_mst_branch_device);
+ }
+
+ static void drm_dp_free_mst_port(struct kref *kref)
+ {
+ struct drm_dp_mst_port *port =
+ container_of(kref, struct drm_dp_mst_port, malloc_kref);
+
+ drm_dp_mst_put_mstb_malloc(port->parent);
+ kfree(port);
+ }
+
+ /**
+ * drm_dp_mst_get_port_malloc() - Increment the malloc refcount of an MST port
+ * @port: The &struct drm_dp_mst_port to increment the malloc refcount of
+ *
+ * Increments &drm_dp_mst_port.malloc_kref. When &drm_dp_mst_port.malloc_kref
+ * reaches 0, the memory allocation for @port will be released and @port may
+ * no longer be used.
+ *
+ * Because @port could potentially be freed at any time by the DP MST helpers
+ * if &drm_dp_mst_port.malloc_kref reaches 0, including during a call to this
+ * function, drivers that which to make use of &struct drm_dp_mst_port should
+ * ensure that they grab at least one main malloc reference to their MST ports
+ * in &drm_dp_mst_topology_cbs.add_connector. This callback is called before
+ * there is any chance for &drm_dp_mst_port.malloc_kref to reach 0.
+ *
+ * See also: drm_dp_mst_put_port_malloc()
+ */
+ void
+ drm_dp_mst_get_port_malloc(struct drm_dp_mst_port *port)
+ {
+ kref_get(&port->malloc_kref);
+ drm_dbg(port->mgr->dev, "port %p (%d)\n", port, kref_read(&port->malloc_kref));
+ }
+ EXPORT_SYMBOL(drm_dp_mst_get_port_malloc);
+
+ /**
+ * drm_dp_mst_put_port_malloc() - Decrement the malloc refcount of an MST port
+ * @port: The &struct drm_dp_mst_port to decrement the malloc refcount of
+ *
+ * Decrements &drm_dp_mst_port.malloc_kref. When &drm_dp_mst_port.malloc_kref
+ * reaches 0, the memory allocation for @port will be released and @port may
+ * no longer be used.
+ *
+ * See also: drm_dp_mst_get_port_malloc()
+ */
+ void
+ drm_dp_mst_put_port_malloc(struct drm_dp_mst_port *port)
+ {
+ drm_dbg(port->mgr->dev, "port %p (%d)\n", port, kref_read(&port->malloc_kref) - 1);
+ kref_put(&port->malloc_kref, drm_dp_free_mst_port);
+ }
+ EXPORT_SYMBOL(drm_dp_mst_put_port_malloc);
+
+ #if IS_ENABLED(CONFIG_DRM_DEBUG_DP_MST_TOPOLOGY_REFS)
+
+ #define STACK_DEPTH 8
+
+ static noinline void
+ __topology_ref_save(struct drm_dp_mst_topology_mgr *mgr,
+ struct drm_dp_mst_topology_ref_history *history,
+ enum drm_dp_mst_topology_ref_type type)
+ {
+ struct drm_dp_mst_topology_ref_entry *entry = NULL;
+ depot_stack_handle_t backtrace;
+ ulong stack_entries[STACK_DEPTH];
+ uint n;
+ int i;
+
+ n = stack_trace_save(stack_entries, ARRAY_SIZE(stack_entries), 1);
+ backtrace = stack_depot_save(stack_entries, n, GFP_KERNEL);
+ if (!backtrace)
+ return;
+
+ /* Try to find an existing entry for this backtrace */
+ for (i = 0; i < history->len; i++) {
+ if (history->entries[i].backtrace == backtrace) {
+ entry = &history->entries[i];
+ break;
+ }
+ }
+
+ /* Otherwise add one */
+ if (!entry) {
+ struct drm_dp_mst_topology_ref_entry *new;
+ int new_len = history->len + 1;
+
+ new = krealloc(history->entries, sizeof(*new) * new_len,
+ GFP_KERNEL);
+ if (!new)
+ return;
+
+ entry = &new[history->len];
+ history->len = new_len;
+ history->entries = new;
+
+ entry->backtrace = backtrace;
+ entry->type = type;
+ entry->count = 0;
+ }
+ entry->count++;
+ entry->ts_nsec = ktime_get_ns();
+ }
+
+ static int
+ topology_ref_history_cmp(const void *a, const void *b)
+ {
+ const struct drm_dp_mst_topology_ref_entry *entry_a = a, *entry_b = b;
+
+ if (entry_a->ts_nsec > entry_b->ts_nsec)
+ return 1;
+ else if (entry_a->ts_nsec < entry_b->ts_nsec)
+ return -1;
+ else
+ return 0;
+ }
+
+ static inline const char *
+ topology_ref_type_to_str(enum drm_dp_mst_topology_ref_type type)
+ {
+ if (type == DRM_DP_MST_TOPOLOGY_REF_GET)
+ return "get";
+ else
+ return "put";
+ }
+
+ static void
+ __dump_topology_ref_history(struct drm_dp_mst_topology_ref_history *history,
+ void *ptr, const char *type_str)
+ {
+ struct drm_printer p = drm_debug_printer(DBG_PREFIX);
+ char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
+ int i;
+
+ if (!buf)
+ return;
+
+ if (!history->len)
+ goto out;
+
+ /* First, sort the list so that it goes from oldest to newest
+ * reference entry
+ */
+ sort(history->entries, history->len, sizeof(*history->entries),
+ topology_ref_history_cmp, NULL);
+
+ drm_printf(&p, "%s (%p) topology count reached 0, dumping history:\n",
+ type_str, ptr);
+
+ for (i = 0; i < history->len; i++) {
+ const struct drm_dp_mst_topology_ref_entry *entry =
+ &history->entries[i];
+ u64 ts_nsec = entry->ts_nsec;
+ u32 rem_nsec = do_div(ts_nsec, 1000000000);
+
+ stack_depot_snprint(entry->backtrace, buf, PAGE_SIZE, 4);
+
+ drm_printf(&p, " %d %ss (last at %5llu.%06u):\n%s",
+ entry->count,
+ topology_ref_type_to_str(entry->type),
+ ts_nsec, rem_nsec / 1000, buf);
+ }
+
+ /* Now free the history, since this is the only time we expose it */
+ kfree(history->entries);
+ out:
+ kfree(buf);
+ }
+
+ static __always_inline void
+ drm_dp_mst_dump_mstb_topology_history(struct drm_dp_mst_branch *mstb)
+ {
+ __dump_topology_ref_history(&mstb->topology_ref_history, mstb,
+ "MSTB");
+ }
+
+ static __always_inline void
+ drm_dp_mst_dump_port_topology_history(struct drm_dp_mst_port *port)
+ {
+ __dump_topology_ref_history(&port->topology_ref_history, port,
+ "Port");
+ }
+
+ static __always_inline void
+ save_mstb_topology_ref(struct drm_dp_mst_branch *mstb,
+ enum drm_dp_mst_topology_ref_type type)
+ {
+ __topology_ref_save(mstb->mgr, &mstb->topology_ref_history, type);
+ }
+
+ static __always_inline void
+ save_port_topology_ref(struct drm_dp_mst_port *port,
+ enum drm_dp_mst_topology_ref_type type)
+ {
+ __topology_ref_save(port->mgr, &port->topology_ref_history, type);
+ }
+
+ static inline void
+ topology_ref_history_lock(struct drm_dp_mst_topology_mgr *mgr)
+ {
+ mutex_lock(&mgr->topology_ref_history_lock);
+ }
+
+ static inline void
+ topology_ref_history_unlock(struct drm_dp_mst_topology_mgr *mgr)
+ {
+ mutex_unlock(&mgr->topology_ref_history_lock);
+ }
+ #else
+ static inline void
+ topology_ref_history_lock(struct drm_dp_mst_topology_mgr *mgr) {}
+ static inline void
+ topology_ref_history_unlock(struct drm_dp_mst_topology_mgr *mgr) {}
+ static inline void
+ drm_dp_mst_dump_mstb_topology_history(struct drm_dp_mst_branch *mstb) {}
+ static inline void
+ drm_dp_mst_dump_port_topology_history(struct drm_dp_mst_port *port) {}
+ #define save_mstb_topology_ref(mstb, type)
+ #define save_port_topology_ref(port, type)
+ #endif
+
+ static void drm_dp_destroy_mst_branch_device(struct kref *kref)
+ {
+ struct drm_dp_mst_branch *mstb =
+ container_of(kref, struct drm_dp_mst_branch, topology_kref);
+ struct drm_dp_mst_topology_mgr *mgr = mstb->mgr;
+
+ drm_dp_mst_dump_mstb_topology_history(mstb);
+
+ INIT_LIST_HEAD(&mstb->destroy_next);
+
+ /*
+ * This can get called under mgr->mutex, so we need to perform the
+ * actual destruction of the mstb in another worker
+ */
+ mutex_lock(&mgr->delayed_destroy_lock);
+ list_add(&mstb->destroy_next, &mgr->destroy_branch_device_list);
+ mutex_unlock(&mgr->delayed_destroy_lock);
+ queue_work(mgr->delayed_destroy_wq, &mgr->delayed_destroy_work);
+ }
+
+ /**
+ * drm_dp_mst_topology_try_get_mstb() - Increment the topology refcount of a
+ * branch device unless it's zero
+ * @mstb: &struct drm_dp_mst_branch to increment the topology refcount of
+ *
+ * Attempts to grab a topology reference to @mstb, if it hasn't yet been
+ * removed from the topology (e.g. &drm_dp_mst_branch.topology_kref has
+ * reached 0). Holding a topology reference implies that a malloc reference
+ * will be held to @mstb as long as the user holds the topology reference.
+ *
+ * Care should be taken to ensure that the user has at least one malloc
+ * reference to @mstb. If you already have a topology reference to @mstb, you
+ * should use drm_dp_mst_topology_get_mstb() instead.
+ *
+ * See also:
+ * drm_dp_mst_topology_get_mstb()
+ * drm_dp_mst_topology_put_mstb()
+ *
+ * Returns:
+ * * 1: A topology reference was grabbed successfully
+ * * 0: @port is no longer in the topology, no reference was grabbed
+ */
+ static int __must_check
+ drm_dp_mst_topology_try_get_mstb(struct drm_dp_mst_branch *mstb)
+ {
+ int ret;
+
+ topology_ref_history_lock(mstb->mgr);
+ ret = kref_get_unless_zero(&mstb->topology_kref);
+ if (ret) {
+ drm_dbg(mstb->mgr->dev, "mstb %p (%d)\n", mstb, kref_read(&mstb->topology_kref));
+ save_mstb_topology_ref(mstb, DRM_DP_MST_TOPOLOGY_REF_GET);
+ }
+
+ topology_ref_history_unlock(mstb->mgr);
+
+ return ret;
+ }
+
+ /**
+ * drm_dp_mst_topology_get_mstb() - Increment the topology refcount of a
+ * branch device
+ * @mstb: The &struct drm_dp_mst_branch to increment the topology refcount of
+ *
+ * Increments &drm_dp_mst_branch.topology_refcount without checking whether or
+ * not it's already reached 0. This is only valid to use in scenarios where
+ * you are already guaranteed to have at least one active topology reference
+ * to @mstb. Otherwise, drm_dp_mst_topology_try_get_mstb() must be used.
+ *
+ * See also:
+ * drm_dp_mst_topology_try_get_mstb()
+ * drm_dp_mst_topology_put_mstb()
+ */
+ static void drm_dp_mst_topology_get_mstb(struct drm_dp_mst_branch *mstb)
+ {
+ topology_ref_history_lock(mstb->mgr);
+
+ save_mstb_topology_ref(mstb, DRM_DP_MST_TOPOLOGY_REF_GET);
+ WARN_ON(kref_read(&mstb->topology_kref) == 0);
+ kref_get(&mstb->topology_kref);
+ drm_dbg(mstb->mgr->dev, "mstb %p (%d)\n", mstb, kref_read(&mstb->topology_kref));
+
+ topology_ref_history_unlock(mstb->mgr);
+ }
+
+ /**
+ * drm_dp_mst_topology_put_mstb() - release a topology reference to a branch
+ * device
+ * @mstb: The &struct drm_dp_mst_branch to release the topology reference from
+ *
+ * Releases a topology reference from @mstb by decrementing
+ * &drm_dp_mst_branch.topology_kref.
+ *
+ * See also:
+ * drm_dp_mst_topology_try_get_mstb()
+ * drm_dp_mst_topology_get_mstb()
+ */
+ static void
+ drm_dp_mst_topology_put_mstb(struct drm_dp_mst_branch *mstb)
+ {
+ topology_ref_history_lock(mstb->mgr);
+
+ drm_dbg(mstb->mgr->dev, "mstb %p (%d)\n", mstb, kref_read(&mstb->topology_kref) - 1);
+ save_mstb_topology_ref(mstb, DRM_DP_MST_TOPOLOGY_REF_PUT);
+
+ topology_ref_history_unlock(mstb->mgr);
+ kref_put(&mstb->topology_kref, drm_dp_destroy_mst_branch_device);
+ }
+
+ static void drm_dp_destroy_port(struct kref *kref)
+ {
+ struct drm_dp_mst_port *port =
+ container_of(kref, struct drm_dp_mst_port, topology_kref);
+ struct drm_dp_mst_topology_mgr *mgr = port->mgr;
+
+ drm_dp_mst_dump_port_topology_history(port);
+
+ /* There's nothing that needs locking to destroy an input port yet */
+ if (port->input) {
+ drm_dp_mst_put_port_malloc(port);
+ return;
+ }
+
+ kfree(port->cached_edid);
+
+ /*
+ * we can't destroy the connector here, as we might be holding the
+ * mode_config.mutex from an EDID retrieval
+ */
+ mutex_lock(&mgr->delayed_destroy_lock);
+ list_add(&port->next, &mgr->destroy_port_list);
+ mutex_unlock(&mgr->delayed_destroy_lock);
+ queue_work(mgr->delayed_destroy_wq, &mgr->delayed_destroy_work);
+ }
+
+ /**
+ * drm_dp_mst_topology_try_get_port() - Increment the topology refcount of a
+ * port unless it's zero
+ * @port: &struct drm_dp_mst_port to increment the topology refcount of
+ *
+ * Attempts to grab a topology reference to @port, if it hasn't yet been
+ * removed from the topology (e.g. &drm_dp_mst_port.topology_kref has reached
+ * 0). Holding a topology reference implies that a malloc reference will be
+ * held to @port as long as the user holds the topology reference.
+ *
+ * Care should be taken to ensure that the user has at least one malloc
+ * reference to @port. If you already have a topology reference to @port, you
+ * should use drm_dp_mst_topology_get_port() instead.
+ *
+ * See also:
+ * drm_dp_mst_topology_get_port()
+ * drm_dp_mst_topology_put_port()
+ *
+ * Returns:
+ * * 1: A topology reference was grabbed successfully
+ * * 0: @port is no longer in the topology, no reference was grabbed
+ */
+ static int __must_check
+ drm_dp_mst_topology_try_get_port(struct drm_dp_mst_port *port)
+ {
+ int ret;
+
+ topology_ref_history_lock(port->mgr);
+ ret = kref_get_unless_zero(&port->topology_kref);
+ if (ret) {
+ drm_dbg(port->mgr->dev, "port %p (%d)\n", port, kref_read(&port->topology_kref));
+ save_port_topology_ref(port, DRM_DP_MST_TOPOLOGY_REF_GET);
+ }
+
+ topology_ref_history_unlock(port->mgr);
+ return ret;
+ }
+
+ /**
+ * drm_dp_mst_topology_get_port() - Increment the topology refcount of a port
+ * @port: The &struct drm_dp_mst_port to increment the topology refcount of
+ *
+ * Increments &drm_dp_mst_port.topology_refcount without checking whether or
+ * not it's already reached 0. This is only valid to use in scenarios where
+ * you are already guaranteed to have at least one active topology reference
+ * to @port. Otherwise, drm_dp_mst_topology_try_get_port() must be used.
+ *
+ * See also:
+ * drm_dp_mst_topology_try_get_port()
+ * drm_dp_mst_topology_put_port()
+ */
+ static void drm_dp_mst_topology_get_port(struct drm_dp_mst_port *port)
+ {
+ topology_ref_history_lock(port->mgr);
+
+ WARN_ON(kref_read(&port->topology_kref) == 0);
+ kref_get(&port->topology_kref);
+ drm_dbg(port->mgr->dev, "port %p (%d)\n", port, kref_read(&port->topology_kref));
+ save_port_topology_ref(port, DRM_DP_MST_TOPOLOGY_REF_GET);
+
+ topology_ref_history_unlock(port->mgr);
+ }
+
+ /**
+ * drm_dp_mst_topology_put_port() - release a topology reference to a port
+ * @port: The &struct drm_dp_mst_port to release the topology reference from
+ *
+ * Releases a topology reference from @port by decrementing
+ * &drm_dp_mst_port.topology_kref.
+ *
+ * See also:
+ * drm_dp_mst_topology_try_get_port()
+ * drm_dp_mst_topology_get_port()
+ */
+ static void drm_dp_mst_topology_put_port(struct drm_dp_mst_port *port)
+ {
+ topology_ref_history_lock(port->mgr);
+
+ drm_dbg(port->mgr->dev, "port %p (%d)\n", port, kref_read(&port->topology_kref) - 1);
+ save_port_topology_ref(port, DRM_DP_MST_TOPOLOGY_REF_PUT);
+
+ topology_ref_history_unlock(port->mgr);
+ kref_put(&port->topology_kref, drm_dp_destroy_port);
+ }
+
+ static struct drm_dp_mst_branch *
+ drm_dp_mst_topology_get_mstb_validated_locked(struct drm_dp_mst_branch *mstb,
+ struct drm_dp_mst_branch *to_find)
+ {
+ struct drm_dp_mst_port *port;
+ struct drm_dp_mst_branch *rmstb;
+
+ if (to_find == mstb)
+ return mstb;
+
+ list_for_each_entry(port, &mstb->ports, next) {
+ if (port->mstb) {
+ rmstb = drm_dp_mst_topology_get_mstb_validated_locked(
+ port->mstb, to_find);
+ if (rmstb)
+ return rmstb;
+ }
+ }
+ return NULL;
+ }
+
+ static struct drm_dp_mst_branch *
+ drm_dp_mst_topology_get_mstb_validated(struct drm_dp_mst_topology_mgr *mgr,
+ struct drm_dp_mst_branch *mstb)
+ {
+ struct drm_dp_mst_branch *rmstb = NULL;
+
+ mutex_lock(&mgr->lock);
+ if (mgr->mst_primary) {
+ rmstb = drm_dp_mst_topology_get_mstb_validated_locked(
+ mgr->mst_primary, mstb);
+
+ if (rmstb && !drm_dp_mst_topology_try_get_mstb(rmstb))
+ rmstb = NULL;
+ }
+ mutex_unlock(&mgr->lock);
+ return rmstb;
+ }
+
+ static struct drm_dp_mst_port *
+ drm_dp_mst_topology_get_port_validated_locked(struct drm_dp_mst_branch *mstb,
+ struct drm_dp_mst_port *to_find)
+ {
+ struct drm_dp_mst_port *port, *mport;
+
+ list_for_each_entry(port, &mstb->ports, next) {
+ if (port == to_find)
+ return port;
+
+ if (port->mstb) {
+ mport = drm_dp_mst_topology_get_port_validated_locked(
+ port->mstb, to_find);
+ if (mport)
+ return mport;
+ }
+ }
+ return NULL;
+ }
+
+ static struct drm_dp_mst_port *
+ drm_dp_mst_topology_get_port_validated(struct drm_dp_mst_topology_mgr *mgr,
+ struct drm_dp_mst_port *port)
+ {
+ struct drm_dp_mst_port *rport = NULL;
+
+ mutex_lock(&mgr->lock);
+ if (mgr->mst_primary) {
+ rport = drm_dp_mst_topology_get_port_validated_locked(
+ mgr->mst_primary, port);
+
+ if (rport && !drm_dp_mst_topology_try_get_port(rport))
+ rport = NULL;
+ }
+ mutex_unlock(&mgr->lock);
+ return rport;
+ }
+
+ static struct drm_dp_mst_port *drm_dp_get_port(struct drm_dp_mst_branch *mstb, u8 port_num)
+ {
+ struct drm_dp_mst_port *port;
+ int ret;
+
+ list_for_each_entry(port, &mstb->ports, next) {
+ if (port->port_num == port_num) {
+ ret = drm_dp_mst_topology_try_get_port(port);
+ return ret ? port : NULL;
+ }
+ }
+
+ return NULL;
+ }
+
+ /*
+ * calculate a new RAD for this MST branch device
+ * if parent has an LCT of 2 then it has 1 nibble of RAD,
+ * if parent has an LCT of 3 then it has 2 nibbles of RAD,
+ */
+ static u8 drm_dp_calculate_rad(struct drm_dp_mst_port *port,
+ u8 *rad)
+ {
+ int parent_lct = port->parent->lct;
+ int shift = 4;
+ int idx = (parent_lct - 1) / 2;
+
+ if (parent_lct > 1) {
+ memcpy(rad, port->parent->rad, idx + 1);
+ shift = (parent_lct % 2) ? 4 : 0;
+ } else
+ rad[0] = 0;
+
+ rad[idx] |= port->port_num << shift;
+ return parent_lct + 1;
+ }
+
+ static bool drm_dp_mst_is_end_device(u8 pdt, bool mcs)
+ {
+ switch (pdt) {
+ case DP_PEER_DEVICE_DP_LEGACY_CONV:
+ case DP_PEER_DEVICE_SST_SINK:
+ return true;
+ case DP_PEER_DEVICE_MST_BRANCHING:
+ /* For sst branch device */
+ if (!mcs)
+ return true;
+
+ return false;
+ }
+ return true;
+ }
+
+ static int
+ drm_dp_port_set_pdt(struct drm_dp_mst_port *port, u8 new_pdt,
+ bool new_mcs)
+ {
+ struct drm_dp_mst_topology_mgr *mgr = port->mgr;
+ struct drm_dp_mst_branch *mstb;
+ u8 rad[8], lct;
+ int ret = 0;
+
+ if (port->pdt == new_pdt && port->mcs == new_mcs)
+ return 0;
+
+ /* Teardown the old pdt, if there is one */
+ if (port->pdt != DP_PEER_DEVICE_NONE) {
+ if (drm_dp_mst_is_end_device(port->pdt, port->mcs)) {
+ /*
+ * If the new PDT would also have an i2c bus,
+ * don't bother with reregistering it
+ */
+ if (new_pdt != DP_PEER_DEVICE_NONE &&
+ drm_dp_mst_is_end_device(new_pdt, new_mcs)) {
+ port->pdt = new_pdt;
+ port->mcs = new_mcs;
+ return 0;
+ }
+
+ /* remove i2c over sideband */
+ drm_dp_mst_unregister_i2c_bus(port);
+ } else {
+ mutex_lock(&mgr->lock);
+ drm_dp_mst_topology_put_mstb(port->mstb);
+ port->mstb = NULL;
+ mutex_unlock(&mgr->lock);
+ }
+ }
+
+ port->pdt = new_pdt;
+ port->mcs = new_mcs;
+
+ if (port->pdt != DP_PEER_DEVICE_NONE) {
+ if (drm_dp_mst_is_end_device(port->pdt, port->mcs)) {
+ /* add i2c over sideband */
+ ret = drm_dp_mst_register_i2c_bus(port);
+ } else {
+ lct = drm_dp_calculate_rad(port, rad);
+ mstb = drm_dp_add_mst_branch_device(lct, rad);
+ if (!mstb) {
+ ret = -ENOMEM;
+ drm_err(mgr->dev, "Failed to create MSTB for port %p", port);
+ goto out;
+ }
+
+ mutex_lock(&mgr->lock);
+ port->mstb = mstb;
+ mstb->mgr = port->mgr;
+ mstb->port_parent = port;
+
+ /*
+ * Make sure this port's memory allocation stays
+ * around until its child MSTB releases it
+ */
+ drm_dp_mst_get_port_malloc(port);
+ mutex_unlock(&mgr->lock);
+
+ /* And make sure we send a link address for this */
+ ret = 1;
+ }
+ }
+
+ out:
+ if (ret < 0)
+ port->pdt = DP_PEER_DEVICE_NONE;
+ return ret;
+ }
+
+ /**
+ * drm_dp_mst_dpcd_read() - read a series of bytes from the DPCD via sideband
+ * @aux: Fake sideband AUX CH
+ * @offset: address of the (first) register to read
+ * @buffer: buffer to store the register values
+ * @size: number of bytes in @buffer
+ *
+ * Performs the same functionality for remote devices via
+ * sideband messaging as drm_dp_dpcd_read() does for local
+ * devices via actual AUX CH.
+ *
+ * Return: Number of bytes read, or negative error code on failure.
+ */
+ ssize_t drm_dp_mst_dpcd_read(struct drm_dp_aux *aux,
+ unsigned int offset, void *buffer, size_t size)
+ {
+ struct drm_dp_mst_port *port = container_of(aux, struct drm_dp_mst_port,
+ aux);
+
+ return drm_dp_send_dpcd_read(port->mgr, port,
+ offset, size, buffer);
+ }
+
+ /**
+ * drm_dp_mst_dpcd_write() - write a series of bytes to the DPCD via sideband
+ * @aux: Fake sideband AUX CH
+ * @offset: address of the (first) register to write
+ * @buffer: buffer containing the values to write
+ * @size: number of bytes in @buffer
+ *
+ * Performs the same functionality for remote devices via
+ * sideband messaging as drm_dp_dpcd_write() does for local
+ * devices via actual AUX CH.
+ *
+ * Return: number of bytes written on success, negative error code on failure.
+ */
+ ssize_t drm_dp_mst_dpcd_write(struct drm_dp_aux *aux,
+ unsigned int offset, void *buffer, size_t size)
+ {
+ struct drm_dp_mst_port *port = container_of(aux, struct drm_dp_mst_port,
+ aux);
+
+ return drm_dp_send_dpcd_write(port->mgr, port,
+ offset, size, buffer);
+ }
+
+ static int drm_dp_check_mstb_guid(struct drm_dp_mst_branch *mstb, u8 *guid)
+ {
+ int ret = 0;
+
+ memcpy(mstb->guid, guid, 16);
+
+ if (!drm_dp_validate_guid(mstb->mgr, mstb->guid)) {
+ if (mstb->port_parent) {
+ ret = drm_dp_send_dpcd_write(mstb->mgr,
+ mstb->port_parent,
+ DP_GUID, 16, mstb->guid);
+ } else {
+ ret = drm_dp_dpcd_write(mstb->mgr->aux,
+ DP_GUID, mstb->guid, 16);
+ }
+ }
+
+ if (ret < 16 && ret > 0)
+ return -EPROTO;
+
+ return ret == 16 ? 0 : ret;
+ }
+
+ static void build_mst_prop_path(const struct drm_dp_mst_branch *mstb,
+ int pnum,
+ char *proppath,
+ size_t proppath_size)
+ {
+ int i;
+ char temp[8];
+
+ snprintf(proppath, proppath_size, "mst:%d", mstb->mgr->conn_base_id);
+ for (i = 0; i < (mstb->lct - 1); i++) {
+ int shift = (i % 2) ? 0 : 4;
+ int port_num = (mstb->rad[i / 2] >> shift) & 0xf;
+
+ snprintf(temp, sizeof(temp), "-%d", port_num);
+ strlcat(proppath, temp, proppath_size);
+ }
+ snprintf(temp, sizeof(temp), "-%d", pnum);
+ strlcat(proppath, temp, proppath_size);
+ }
+
+ /**
+ * drm_dp_mst_connector_late_register() - Late MST connector registration
+ * @connector: The MST connector
+ * @port: The MST port for this connector
+ *
+ * Helper to register the remote aux device for this MST port. Drivers should
+ * call this from their mst connector's late_register hook to enable MST aux
+ * devices.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+ int drm_dp_mst_connector_late_register(struct drm_connector *connector,
+ struct drm_dp_mst_port *port)
+ {
+ drm_dbg_kms(port->mgr->dev, "registering %s remote bus for %s\n",
+ port->aux.name, connector->kdev->kobj.name);
+
+ port->aux.dev = connector->kdev;
+ return drm_dp_aux_register_devnode(&port->aux);
+ }
+ EXPORT_SYMBOL(drm_dp_mst_connector_late_register);
+
+ /**
+ * drm_dp_mst_connector_early_unregister() - Early MST connector unregistration
+ * @connector: The MST connector
+ * @port: The MST port for this connector
+ *
+ * Helper to unregister the remote aux device for this MST port, registered by
+ * drm_dp_mst_connector_late_register(). Drivers should call this from their mst
+ * connector's early_unregister hook.
+ */
+ void drm_dp_mst_connector_early_unregister(struct drm_connector *connector,
+ struct drm_dp_mst_port *port)
+ {
+ drm_dbg_kms(port->mgr->dev, "unregistering %s remote bus for %s\n",
+ port->aux.name, connector->kdev->kobj.name);
+ drm_dp_aux_unregister_devnode(&port->aux);
+ }
+ EXPORT_SYMBOL(drm_dp_mst_connector_early_unregister);
+
+ static void
+ drm_dp_mst_port_add_connector(struct drm_dp_mst_branch *mstb,
+ struct drm_dp_mst_port *port)
+ {
+ struct drm_dp_mst_topology_mgr *mgr = port->mgr;
+ char proppath[255];
+ int ret;
+
+ build_mst_prop_path(mstb, port->port_num, proppath, sizeof(proppath));
+ port->connector = mgr->cbs->add_connector(mgr, port, proppath);
+ if (!port->connector) {
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ if (port->pdt != DP_PEER_DEVICE_NONE &&
+ drm_dp_mst_is_end_device(port->pdt, port->mcs) &&
+ port->port_num >= DP_MST_LOGICAL_PORT_0)
+ port->cached_edid = drm_get_edid(port->connector,
+ &port->aux.ddc);
+
+ drm_connector_register(port->connector);
+ return;
+
+ error:
+ drm_err(mgr->dev, "Failed to create connector for port %p: %d\n", port, ret);
+ }
+
+ /*
+ * Drop a topology reference, and unlink the port from the in-memory topology
+ * layout
+ */
+ static void
+ drm_dp_mst_topology_unlink_port(struct drm_dp_mst_topology_mgr *mgr,
+ struct drm_dp_mst_port *port)
+ {
+ mutex_lock(&mgr->lock);
+ port->parent->num_ports--;
+ list_del(&port->next);
+ mutex_unlock(&mgr->lock);
+ drm_dp_mst_topology_put_port(port);
+ }
+
+ static struct drm_dp_mst_port *
+ drm_dp_mst_add_port(struct drm_device *dev,
+ struct drm_dp_mst_topology_mgr *mgr,
+ struct drm_dp_mst_branch *mstb, u8 port_number)
+ {
+ struct drm_dp_mst_port *port = kzalloc(sizeof(*port), GFP_KERNEL);
+
+ if (!port)
+ return NULL;
+
+ kref_init(&port->topology_kref);
+ kref_init(&port->malloc_kref);
+ port->parent = mstb;
+ port->port_num = port_number;
+ port->mgr = mgr;
+ port->aux.name = "DPMST";
+ port->aux.dev = dev->dev;
+ port->aux.is_remote = true;
+
+ /* initialize the MST downstream port's AUX crc work queue */
+ port->aux.drm_dev = dev;
+ drm_dp_remote_aux_init(&port->aux);
+
+ /*
+ * Make sure the memory allocation for our parent branch stays
+ * around until our own memory allocation is released
+ */
+ drm_dp_mst_get_mstb_malloc(mstb);
+
+ return port;
+ }
+
+ static int
+ drm_dp_mst_handle_link_address_port(struct drm_dp_mst_branch *mstb,
+ struct drm_device *dev,
+ struct drm_dp_link_addr_reply_port *port_msg)
+ {
+ struct drm_dp_mst_topology_mgr *mgr = mstb->mgr;
+ struct drm_dp_mst_port *port;
+ int old_ddps = 0, ret;
+ u8 new_pdt = DP_PEER_DEVICE_NONE;
+ bool new_mcs = 0;
+ bool created = false, send_link_addr = false, changed = false;
+
+ port = drm_dp_get_port(mstb, port_msg->port_number);
+ if (!port) {
+ port = drm_dp_mst_add_port(dev, mgr, mstb,
+ port_msg->port_number);
+ if (!port)
+ return -ENOMEM;
+ created = true;
+ changed = true;
+ } else if (!port->input && port_msg->input_port && port->connector) {
+ /* Since port->connector can't be changed here, we create a
+ * new port if input_port changes from 0 to 1
+ */
+ drm_dp_mst_topology_unlink_port(mgr, port);
+ drm_dp_mst_topology_put_port(port);
+ port = drm_dp_mst_add_port(dev, mgr, mstb,
+ port_msg->port_number);
+ if (!port)
+ return -ENOMEM;
+ changed = true;
+ created = true;
+ } else if (port->input && !port_msg->input_port) {
+ changed = true;
+ } else if (port->connector) {
+ /* We're updating a port that's exposed to userspace, so do it
+ * under lock
+ */
+ drm_modeset_lock(&mgr->base.lock, NULL);
+
+ old_ddps = port->ddps;
+ changed = port->ddps != port_msg->ddps ||
+ (port->ddps &&
+ (port->ldps != port_msg->legacy_device_plug_status ||
+ port->dpcd_rev != port_msg->dpcd_revision ||
+ port->mcs != port_msg->mcs ||
+ port->pdt != port_msg->peer_device_type ||
+ port->num_sdp_stream_sinks !=
+ port_msg->num_sdp_stream_sinks));
+ }
+
+ port->input = port_msg->input_port;
+ if (!port->input)
+ new_pdt = port_msg->peer_device_type;
+ new_mcs = port_msg->mcs;
+ port->ddps = port_msg->ddps;
+ port->ldps = port_msg->legacy_device_plug_status;
+ port->dpcd_rev = port_msg->dpcd_revision;
+ port->num_sdp_streams = port_msg->num_sdp_streams;
+ port->num_sdp_stream_sinks = port_msg->num_sdp_stream_sinks;
+
+ /* manage mstb port lists with mgr lock - take a reference
+ for this list */
+ if (created) {
+ mutex_lock(&mgr->lock);
+ drm_dp_mst_topology_get_port(port);
+ list_add(&port->next, &mstb->ports);
+ mstb->num_ports++;
+ mutex_unlock(&mgr->lock);
+ }
+
+ /*
+ * Reprobe PBN caps on both hotplug, and when re-probing the link
+ * for our parent mstb
+ */
+ if (old_ddps != port->ddps || !created) {
+ if (port->ddps && !port->input) {
+ ret = drm_dp_send_enum_path_resources(mgr, mstb,
+ port);
+ if (ret == 1)
+ changed = true;
+ } else {
+ port->full_pbn = 0;
+ }
+ }
+
+ ret = drm_dp_port_set_pdt(port, new_pdt, new_mcs);
+ if (ret == 1) {
+ send_link_addr = true;
+ } else if (ret < 0) {
+ drm_err(dev, "Failed to change PDT on port %p: %d\n", port, ret);
+ goto fail;
+ }
+
+ /*
+ * If this port wasn't just created, then we're reprobing because
+ * we're coming out of suspend. In this case, always resend the link
+ * address if there's an MSTB on this port
+ */
+ if (!created && port->pdt == DP_PEER_DEVICE_MST_BRANCHING &&
+ port->mcs)
+ send_link_addr = true;
+
+ if (port->connector)
+ drm_modeset_unlock(&mgr->base.lock);
+ else if (!port->input)
+ drm_dp_mst_port_add_connector(mstb, port);
+
+ if (send_link_addr && port->mstb) {
+ ret = drm_dp_send_link_address(mgr, port->mstb);
+ if (ret == 1) /* MSTB below us changed */
+ changed = true;
+ else if (ret < 0)
+ goto fail_put;
+ }
+
+ /* put reference to this port */
+ drm_dp_mst_topology_put_port(port);
+ return changed;
+
+ fail:
+ drm_dp_mst_topology_unlink_port(mgr, port);
+ if (port->connector)
+ drm_modeset_unlock(&mgr->base.lock);
+ fail_put:
+ drm_dp_mst_topology_put_port(port);
+ return ret;
+ }
+
+ static void
+ drm_dp_mst_handle_conn_stat(struct drm_dp_mst_branch *mstb,
+ struct drm_dp_connection_status_notify *conn_stat)
+ {
+ struct drm_dp_mst_topology_mgr *mgr = mstb->mgr;
+ struct drm_dp_mst_port *port;
+ int old_ddps, ret;
+ u8 new_pdt;
+ bool new_mcs;
+ bool dowork = false, create_connector = false;
+
+ port = drm_dp_get_port(mstb, conn_stat->port_number);
+ if (!port)
+ return;
+
+ if (port->connector) {
+ if (!port->input && conn_stat->input_port) {
+ /*
+ * We can't remove a connector from an already exposed
+ * port, so just throw the port out and make sure we
+ * reprobe the link address of it's parent MSTB
+ */
+ drm_dp_mst_topology_unlink_port(mgr, port);
+ mstb->link_address_sent = false;
+ dowork = true;
+ goto out;
+ }
+
+ /* Locking is only needed if the port's exposed to userspace */
+ drm_modeset_lock(&mgr->base.lock, NULL);
+ } else if (port->input && !conn_stat->input_port) {
+ create_connector = true;
+ /* Reprobe link address so we get num_sdp_streams */
+ mstb->link_address_sent = false;
+ dowork = true;
+ }
+
+ old_ddps = port->ddps;
+ port->input = conn_stat->input_port;
+ port->ldps = conn_stat->legacy_device_plug_status;
+ port->ddps = conn_stat->displayport_device_plug_status;
+
+ if (old_ddps != port->ddps) {
+ if (port->ddps && !port->input)
+ drm_dp_send_enum_path_resources(mgr, mstb, port);
+ else
+ port->full_pbn = 0;
+ }
+
+ new_pdt = port->input ? DP_PEER_DEVICE_NONE : conn_stat->peer_device_type;
+ new_mcs = conn_stat->message_capability_status;
+ ret = drm_dp_port_set_pdt(port, new_pdt, new_mcs);
+ if (ret == 1) {
+ dowork = true;
+ } else if (ret < 0) {
+ drm_err(mgr->dev, "Failed to change PDT for port %p: %d\n", port, ret);
+ dowork = false;
+ }
+
+ if (port->connector)
+ drm_modeset_unlock(&mgr->base.lock);
+ else if (create_connector)
+ drm_dp_mst_port_add_connector(mstb, port);
+
+ out:
+ drm_dp_mst_topology_put_port(port);
+ if (dowork)
+ queue_work(system_long_wq, &mstb->mgr->work);
+ }
+
+ static struct drm_dp_mst_branch *drm_dp_get_mst_branch_device(struct drm_dp_mst_topology_mgr *mgr,
+ u8 lct, u8 *rad)
+ {
+ struct drm_dp_mst_branch *mstb;
+ struct drm_dp_mst_port *port;
+ int i, ret;
+ /* find the port by iterating down */
+
+ mutex_lock(&mgr->lock);
+ mstb = mgr->mst_primary;
+
+ if (!mstb)
+ goto out;
+
+ for (i = 0; i < lct - 1; i++) {
+ int shift = (i % 2) ? 0 : 4;
+ int port_num = (rad[i / 2] >> shift) & 0xf;
+
+ list_for_each_entry(port, &mstb->ports, next) {
+ if (port->port_num == port_num) {
+ mstb = port->mstb;
+ if (!mstb) {
+ drm_err(mgr->dev,
+ "failed to lookup MSTB with lct %d, rad %02x\n",
+ lct, rad[0]);
+ goto out;
+ }
+
+ break;
+ }
+ }
+ }
+ ret = drm_dp_mst_topology_try_get_mstb(mstb);
+ if (!ret)
+ mstb = NULL;
+ out:
+ mutex_unlock(&mgr->lock);
+ return mstb;
+ }
+
+ static struct drm_dp_mst_branch *get_mst_branch_device_by_guid_helper(
+ struct drm_dp_mst_branch *mstb,
+ const uint8_t *guid)
+ {
+ struct drm_dp_mst_branch *found_mstb;
+ struct drm_dp_mst_port *port;
+
+ if (memcmp(mstb->guid, guid, 16) == 0)
+ return mstb;
+
+
+ list_for_each_entry(port, &mstb->ports, next) {
+ if (!port->mstb)
+ continue;
+
+ found_mstb = get_mst_branch_device_by_guid_helper(port->mstb, guid);
+
+ if (found_mstb)
+ return found_mstb;
+ }
+
+ return NULL;
+ }
+
+ static struct drm_dp_mst_branch *
+ drm_dp_get_mst_branch_device_by_guid(struct drm_dp_mst_topology_mgr *mgr,
+ const uint8_t *guid)
+ {
+ struct drm_dp_mst_branch *mstb;
+ int ret;
+
+ /* find the port by iterating down */
+ mutex_lock(&mgr->lock);
+
+ mstb = get_mst_branch_device_by_guid_helper(mgr->mst_primary, guid);
+ if (mstb) {
+ ret = drm_dp_mst_topology_try_get_mstb(mstb);
+ if (!ret)
+ mstb = NULL;
+ }
+
+ mutex_unlock(&mgr->lock);
+ return mstb;
+ }
+
+ static int drm_dp_check_and_send_link_address(struct drm_dp_mst_topology_mgr *mgr,
+ struct drm_dp_mst_branch *mstb)
+ {
+ struct drm_dp_mst_port *port;
+ int ret;
+ bool changed = false;
+
+ if (!mstb->link_address_sent) {
+ ret = drm_dp_send_link_address(mgr, mstb);
+ if (ret == 1)
+ changed = true;
+ else if (ret < 0)
+ return ret;
+ }
+
+ list_for_each_entry(port, &mstb->ports, next) {
+ struct drm_dp_mst_branch *mstb_child = NULL;
+
+ if (port->input || !port->ddps)
+ continue;
+
+ if (port->mstb)
+ mstb_child = drm_dp_mst_topology_get_mstb_validated(
+ mgr, port->mstb);
+
+ if (mstb_child) {
+ ret = drm_dp_check_and_send_link_address(mgr,
+ mstb_child);
+ drm_dp_mst_topology_put_mstb(mstb_child);
+ if (ret == 1)
+ changed = true;
+ else if (ret < 0)
+ return ret;
+ }
+ }
+
+ return changed;
+ }
+
+ static void drm_dp_mst_link_probe_work(struct work_struct *work)
+ {
+ struct drm_dp_mst_topology_mgr *mgr =
+ container_of(work, struct drm_dp_mst_topology_mgr, work);
+ struct drm_device *dev = mgr->dev;
+ struct drm_dp_mst_branch *mstb;
+ int ret;
+ bool clear_payload_id_table;
+
+ mutex_lock(&mgr->probe_lock);
+
+ mutex_lock(&mgr->lock);
+ clear_payload_id_table = !mgr->payload_id_table_cleared;
+ mgr->payload_id_table_cleared = true;
+
+ mstb = mgr->mst_primary;
+ if (mstb) {
+ ret = drm_dp_mst_topology_try_get_mstb(mstb);
+ if (!ret)
+ mstb = NULL;
+ }
+ mutex_unlock(&mgr->lock);
+ if (!mstb) {
+ mutex_unlock(&mgr->probe_lock);
+ return;
+ }
+
+ /*
+ * Certain branch devices seem to incorrectly report an available_pbn
+ * of 0 on downstream sinks, even after clearing the
+ * DP_PAYLOAD_ALLOCATE_* registers in
+ * drm_dp_mst_topology_mgr_set_mst(). Namely, the CableMatters USB-C
+ * 2x DP hub. Sending a CLEAR_PAYLOAD_ID_TABLE message seems to make
+ * things work again.
+ */
+ if (clear_payload_id_table) {
+ drm_dbg_kms(dev, "Clearing payload ID table\n");
+ drm_dp_send_clear_payload_id_table(mgr, mstb);
+ }
+
+ ret = drm_dp_check_and_send_link_address(mgr, mstb);
+ drm_dp_mst_topology_put_mstb(mstb);
+
+ mutex_unlock(&mgr->probe_lock);
+ if (ret > 0)
+ drm_kms_helper_hotplug_event(dev);
+ }
+
+ static bool drm_dp_validate_guid(struct drm_dp_mst_topology_mgr *mgr,
+ u8 *guid)
+ {
+ u64 salt;
+
+ if (memchr_inv(guid, 0, 16))
+ return true;
+
+ salt = get_jiffies_64();
+
+ memcpy(&guid[0], &salt, sizeof(u64));
+ memcpy(&guid[8], &salt, sizeof(u64));
+
+ return false;
+ }
+
+ static void build_dpcd_read(struct drm_dp_sideband_msg_tx *msg,
+ u8 port_num, u32 offset, u8 num_bytes)
+ {
+ struct drm_dp_sideband_msg_req_body req;
+
+ req.req_type = DP_REMOTE_DPCD_READ;
+ req.u.dpcd_read.port_number = port_num;
+ req.u.dpcd_read.dpcd_address = offset;
+ req.u.dpcd_read.num_bytes = num_bytes;
+ drm_dp_encode_sideband_req(&req, msg);
+ }
+
+ static int drm_dp_send_sideband_msg(struct drm_dp_mst_topology_mgr *mgr,
+ bool up, u8 *msg, int len)
+ {
+ int ret;
+ int regbase = up ? DP_SIDEBAND_MSG_UP_REP_BASE : DP_SIDEBAND_MSG_DOWN_REQ_BASE;
+ int tosend, total, offset;
+ int retries = 0;
+
+ retry:
+ total = len;
+ offset = 0;
+ do {
+ tosend = min3(mgr->max_dpcd_transaction_bytes, 16, total);
+
+ ret = drm_dp_dpcd_write(mgr->aux, regbase + offset,
+ &msg[offset],
+ tosend);
+ if (ret != tosend) {
+ if (ret == -EIO && retries < 5) {
+ retries++;
+ goto retry;
+ }
+ drm_dbg_kms(mgr->dev, "failed to dpcd write %d %d\n", tosend, ret);
+
+ return -EIO;
+ }
+ offset += tosend;
+ total -= tosend;
+ } while (total > 0);
+ return 0;
+ }
+
+ static int set_hdr_from_dst_qlock(struct drm_dp_sideband_msg_hdr *hdr,
+ struct drm_dp_sideband_msg_tx *txmsg)
+ {
+ struct drm_dp_mst_branch *mstb = txmsg->dst;
+ u8 req_type;
+
+ req_type = txmsg->msg[0] & 0x7f;
+ if (req_type == DP_CONNECTION_STATUS_NOTIFY ||
+ req_type == DP_RESOURCE_STATUS_NOTIFY ||
+ req_type == DP_CLEAR_PAYLOAD_ID_TABLE)
+ hdr->broadcast = 1;
+ else
+ hdr->broadcast = 0;
+ hdr->path_msg = txmsg->path_msg;
+ if (hdr->broadcast) {
+ hdr->lct = 1;
+ hdr->lcr = 6;
+ } else {
+ hdr->lct = mstb->lct;
+ hdr->lcr = mstb->lct - 1;
+ }
+
+ memcpy(hdr->rad, mstb->rad, hdr->lct / 2);
+
+ return 0;
+ }
+ /*
+ * process a single block of the next message in the sideband queue
+ */
+ static int process_single_tx_qlock(struct drm_dp_mst_topology_mgr *mgr,
+ struct drm_dp_sideband_msg_tx *txmsg,
+ bool up)
+ {
+ u8 chunk[48];
+ struct drm_dp_sideband_msg_hdr hdr;
+ int len, space, idx, tosend;
+ int ret;
+
+ if (txmsg->state == DRM_DP_SIDEBAND_TX_SENT)
+ return 0;
+
+ memset(&hdr, 0, sizeof(struct drm_dp_sideband_msg_hdr));
+
+ if (txmsg->state == DRM_DP_SIDEBAND_TX_QUEUED)
+ txmsg->state = DRM_DP_SIDEBAND_TX_START_SEND;
+
+ /* make hdr from dst mst */
+ ret = set_hdr_from_dst_qlock(&hdr, txmsg);
+ if (ret < 0)
+ return ret;
+
+ /* amount left to send in this message */
+ len = txmsg->cur_len - txmsg->cur_offset;
+
+ /* 48 - sideband msg size - 1 byte for data CRC, x header bytes */
+ space = 48 - 1 - drm_dp_calc_sb_hdr_size(&hdr);
+
+ tosend = min(len, space);
+ if (len == txmsg->cur_len)
+ hdr.somt = 1;
+ if (space >= len)
+ hdr.eomt = 1;
+
+
+ hdr.msg_len = tosend + 1;
+ drm_dp_encode_sideband_msg_hdr(&hdr, chunk, &idx);
+ memcpy(&chunk[idx], &txmsg->msg[txmsg->cur_offset], tosend);
+ /* add crc at end */
+ drm_dp_crc_sideband_chunk_req(&chunk[idx], tosend);
+ idx += tosend + 1;
+
+ ret = drm_dp_send_sideband_msg(mgr, up, chunk, idx);
+ if (ret) {
+ if (drm_debug_enabled(DRM_UT_DP)) {
+ struct drm_printer p = drm_debug_printer(DBG_PREFIX);
+
+ drm_printf(&p, "sideband msg failed to send\n");
+ drm_dp_mst_dump_sideband_msg_tx(&p, txmsg);
+ }
+ return ret;
+ }
+
+ txmsg->cur_offset += tosend;
+ if (txmsg->cur_offset == txmsg->cur_len) {
+ txmsg->state = DRM_DP_SIDEBAND_TX_SENT;
+ return 1;
+ }
+ return 0;
+ }
+
+ static void process_single_down_tx_qlock(struct drm_dp_mst_topology_mgr *mgr)
+ {
+ struct drm_dp_sideband_msg_tx *txmsg;
+ int ret;
+
+ WARN_ON(!mutex_is_locked(&mgr->qlock));
+
+ /* construct a chunk from the first msg in the tx_msg queue */
+ if (list_empty(&mgr->tx_msg_downq))
+ return;
+
+ txmsg = list_first_entry(&mgr->tx_msg_downq,
+ struct drm_dp_sideband_msg_tx, next);
+ ret = process_single_tx_qlock(mgr, txmsg, false);
+ if (ret < 0) {
+ drm_dbg_kms(mgr->dev, "failed to send msg in q %d\n", ret);
+ list_del(&txmsg->next);
+ txmsg->state = DRM_DP_SIDEBAND_TX_TIMEOUT;
+ wake_up_all(&mgr->tx_waitq);
+ }
+ }
+
+ static void drm_dp_queue_down_tx(struct drm_dp_mst_topology_mgr *mgr,
+ struct drm_dp_sideband_msg_tx *txmsg)
+ {
+ mutex_lock(&mgr->qlock);
+ list_add_tail(&txmsg->next, &mgr->tx_msg_downq);
+
+ if (drm_debug_enabled(DRM_UT_DP)) {
+ struct drm_printer p = drm_debug_printer(DBG_PREFIX);
+
+ drm_dp_mst_dump_sideband_msg_tx(&p, txmsg);
+ }
+
+ if (list_is_singular(&mgr->tx_msg_downq))
+ process_single_down_tx_qlock(mgr);
+ mutex_unlock(&mgr->qlock);
+ }
+
+ static void
+ drm_dp_dump_link_address(const struct drm_dp_mst_topology_mgr *mgr,
+ struct drm_dp_link_address_ack_reply *reply)
+ {
+ struct drm_dp_link_addr_reply_port *port_reply;
+ int i;
+
+ for (i = 0; i < reply->nports; i++) {
+ port_reply = &reply->ports[i];
+ drm_dbg_kms(mgr->dev,
+ "port %d: input %d, pdt: %d, pn: %d, dpcd_rev: %02x, mcs: %d, ddps: %d, ldps %d, sdp %d/%d\n",
+ i,
+ port_reply->input_port,
+ port_reply->peer_device_type,
+ port_reply->port_number,
+ port_reply->dpcd_revision,
+ port_reply->mcs,
+ port_reply->ddps,
+ port_reply->legacy_device_plug_status,
+ port_reply->num_sdp_streams,
+ port_reply->num_sdp_stream_sinks);
+ }
+ }
+
+ static int drm_dp_send_link_address(struct drm_dp_mst_topology_mgr *mgr,
+ struct drm_dp_mst_branch *mstb)
+ {
+ struct drm_dp_sideband_msg_tx *txmsg;
+ struct drm_dp_link_address_ack_reply *reply;
+ struct drm_dp_mst_port *port, *tmp;
+ int i, ret, port_mask = 0;
+ bool changed = false;
+
+ txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL);
+ if (!txmsg)
+ return -ENOMEM;
+
+ txmsg->dst = mstb;
+ build_link_address(txmsg);
+
+ mstb->link_address_sent = true;
+ drm_dp_queue_down_tx(mgr, txmsg);
+
+ /* FIXME: Actually do some real error handling here */
+ ret = drm_dp_mst_wait_tx_reply(mstb, txmsg);
+ if (ret <= 0) {
+ drm_err(mgr->dev, "Sending link address failed with %d\n", ret);
+ goto out;
+ }
+ if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) {
+ drm_err(mgr->dev, "link address NAK received\n");
+ ret = -EIO;
+ goto out;
+ }
+
+ reply = &txmsg->reply.u.link_addr;
+ drm_dbg_kms(mgr->dev, "link address reply: %d\n", reply->nports);
+ drm_dp_dump_link_address(mgr, reply);
+
+ ret = drm_dp_check_mstb_guid(mstb, reply->guid);
+ if (ret) {
+ char buf[64];
+
+ drm_dp_mst_rad_to_str(mstb->rad, mstb->lct, buf, sizeof(buf));
+ drm_err(mgr->dev, "GUID check on %s failed: %d\n", buf, ret);
+ goto out;
+ }
+
+ for (i = 0; i < reply->nports; i++) {
+ port_mask |= BIT(reply->ports[i].port_number);
+ ret = drm_dp_mst_handle_link_address_port(mstb, mgr->dev,
+ &reply->ports[i]);
+ if (ret == 1)
+ changed = true;
+ else if (ret < 0)
+ goto out;
+ }
+
+ /* Prune any ports that are currently a part of mstb in our in-memory
+ * topology, but were not seen in this link address. Usually this
+ * means that they were removed while the topology was out of sync,
+ * e.g. during suspend/resume
+ */
+ mutex_lock(&mgr->lock);
+ list_for_each_entry_safe(port, tmp, &mstb->ports, next) {
+ if (port_mask & BIT(port->port_num))
+ continue;
+
+ drm_dbg_kms(mgr->dev, "port %d was not in link address, removing\n",
+ port->port_num);
+ list_del(&port->next);
+ drm_dp_mst_topology_put_port(port);
+ changed = true;
+ }
+ mutex_unlock(&mgr->lock);
+
+ out:
+ if (ret <= 0)
+ mstb->link_address_sent = false;
+ kfree(txmsg);
+ return ret < 0 ? ret : changed;
+ }
+
+ static void
+ drm_dp_send_clear_payload_id_table(struct drm_dp_mst_topology_mgr *mgr,
+ struct drm_dp_mst_branch *mstb)
+ {
+ struct drm_dp_sideband_msg_tx *txmsg;
+ int ret;
+
+ txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL);
+ if (!txmsg)
+ return;
+
+ txmsg->dst = mstb;
+ build_clear_payload_id_table(txmsg);
+
+ drm_dp_queue_down_tx(mgr, txmsg);
+
+ ret = drm_dp_mst_wait_tx_reply(mstb, txmsg);
+ if (ret > 0 && txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK)
+ drm_dbg_kms(mgr->dev, "clear payload table id nak received\n");
+
+ kfree(txmsg);
+ }
+
+ static int
+ drm_dp_send_enum_path_resources(struct drm_dp_mst_topology_mgr *mgr,
+ struct drm_dp_mst_branch *mstb,
+ struct drm_dp_mst_port *port)
+ {
+ struct drm_dp_enum_path_resources_ack_reply *path_res;
+ struct drm_dp_sideband_msg_tx *txmsg;
+ int ret;
+
+ txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL);
+ if (!txmsg)
+ return -ENOMEM;
+
+ txmsg->dst = mstb;
+ build_enum_path_resources(txmsg, port->port_num);
+
+ drm_dp_queue_down_tx(mgr, txmsg);
+
+ ret = drm_dp_mst_wait_tx_reply(mstb, txmsg);
+ if (ret > 0) {
+ ret = 0;
+ path_res = &txmsg->reply.u.path_resources;
+
+ if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) {
+ drm_dbg_kms(mgr->dev, "enum path resources nak received\n");
+ } else {
+ if (port->port_num != path_res->port_number)
+ DRM_ERROR("got incorrect port in response\n");
+
+ drm_dbg_kms(mgr->dev, "enum path resources %d: %d %d\n",
+ path_res->port_number,
+ path_res->full_payload_bw_number,
+ path_res->avail_payload_bw_number);
+
+ /*
+ * If something changed, make sure we send a
+ * hotplug
+ */
+ if (port->full_pbn != path_res->full_payload_bw_number ||
+ port->fec_capable != path_res->fec_capable)
+ ret = 1;
+
+ port->full_pbn = path_res->full_payload_bw_number;
+ port->fec_capable = path_res->fec_capable;
+ }
+ }
+
+ kfree(txmsg);
+ return ret;
+ }
+
+ static struct drm_dp_mst_port *drm_dp_get_last_connected_port_to_mstb(struct drm_dp_mst_branch *mstb)
+ {
+ if (!mstb->port_parent)
+ return NULL;
+
+ if (mstb->port_parent->mstb != mstb)
+ return mstb->port_parent;
+
+ return drm_dp_get_last_connected_port_to_mstb(mstb->port_parent->parent);
+ }
+
+ /*
+ * Searches upwards in the topology starting from mstb to try to find the
+ * closest available parent of mstb that's still connected to the rest of the
+ * topology. This can be used in order to perform operations like releasing
+ * payloads, where the branch device which owned the payload may no longer be
+ * around and thus would require that the payload on the last living relative
+ * be freed instead.
+ */
+ static struct drm_dp_mst_branch *
+ drm_dp_get_last_connected_port_and_mstb(struct drm_dp_mst_topology_mgr *mgr,
+ struct drm_dp_mst_branch *mstb,
+ int *port_num)
+ {
+ struct drm_dp_mst_branch *rmstb = NULL;
+ struct drm_dp_mst_port *found_port;
+
+ mutex_lock(&mgr->lock);
+ if (!mgr->mst_primary)
+ goto out;
+
+ do {
+ found_port = drm_dp_get_last_connected_port_to_mstb(mstb);
+ if (!found_port)
+ break;
+
+ if (drm_dp_mst_topology_try_get_mstb(found_port->parent)) {
+ rmstb = found_port->parent;
+ *port_num = found_port->port_num;
+ } else {
+ /* Search again, starting from this parent */
+ mstb = found_port->parent;
+ }
+ } while (!rmstb);
+ out:
+ mutex_unlock(&mgr->lock);
+ return rmstb;
+ }
+
+ static int drm_dp_payload_send_msg(struct drm_dp_mst_topology_mgr *mgr,
+ struct drm_dp_mst_port *port,
+ int id,
+ int pbn)
+ {
+ struct drm_dp_sideband_msg_tx *txmsg;
+ struct drm_dp_mst_branch *mstb;
+ int ret, port_num;
+ u8 sinks[DRM_DP_MAX_SDP_STREAMS];
+ int i;
+
+ port_num = port->port_num;
+ mstb = drm_dp_mst_topology_get_mstb_validated(mgr, port->parent);
+ if (!mstb) {
+ mstb = drm_dp_get_last_connected_port_and_mstb(mgr,
+ port->parent,
+ &port_num);
+
+ if (!mstb)
+ return -EINVAL;
+ }
+
+ txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL);
+ if (!txmsg) {
+ ret = -ENOMEM;
+ goto fail_put;
+ }
+
+ for (i = 0; i < port->num_sdp_streams; i++)
+ sinks[i] = i;
+
+ txmsg->dst = mstb;
+ build_allocate_payload(txmsg, port_num,
+ id,
+ pbn, port->num_sdp_streams, sinks);
+
+ drm_dp_queue_down_tx(mgr, txmsg);
+
+ /*
+ * FIXME: there is a small chance that between getting the last
+ * connected mstb and sending the payload message, the last connected
+ * mstb could also be removed from the topology. In the future, this
+ * needs to be fixed by restarting the
+ * drm_dp_get_last_connected_port_and_mstb() search in the event of a
+ * timeout if the topology is still connected to the system.
+ */
+ ret = drm_dp_mst_wait_tx_reply(mstb, txmsg);
+ if (ret > 0) {
+ if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK)
+ ret = -EINVAL;
+ else
+ ret = 0;
+ }
+ kfree(txmsg);
+ fail_put:
+ drm_dp_mst_topology_put_mstb(mstb);
+ return ret;
+ }
+
+ int drm_dp_send_power_updown_phy(struct drm_dp_mst_topology_mgr *mgr,
+ struct drm_dp_mst_port *port, bool power_up)
+ {
+ struct drm_dp_sideband_msg_tx *txmsg;
+ int ret;
+
+ port = drm_dp_mst_topology_get_port_validated(mgr, port);
+ if (!port)
+ return -EINVAL;
+
+ txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL);
+ if (!txmsg) {
+ drm_dp_mst_topology_put_port(port);
+ return -ENOMEM;
+ }
+
+ txmsg->dst = port->parent;
+ build_power_updown_phy(txmsg, port->port_num, power_up);
+ drm_dp_queue_down_tx(mgr, txmsg);
+
+ ret = drm_dp_mst_wait_tx_reply(port->parent, txmsg);
+ if (ret > 0) {
+ if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK)
+ ret = -EINVAL;
+ else
+ ret = 0;
+ }
+ kfree(txmsg);
+ drm_dp_mst_topology_put_port(port);
+
+ return ret;
+ }
+ EXPORT_SYMBOL(drm_dp_send_power_updown_phy);
+
+ int drm_dp_send_query_stream_enc_status(struct drm_dp_mst_topology_mgr *mgr,
+ struct drm_dp_mst_port *port,
+ struct drm_dp_query_stream_enc_status_ack_reply *status)
+ {
+ struct drm_dp_sideband_msg_tx *txmsg;
+ u8 nonce[7];
+ int ret;
+
+ txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL);
+ if (!txmsg)
+ return -ENOMEM;
+
+ port = drm_dp_mst_topology_get_port_validated(mgr, port);
+ if (!port) {
+ ret = -EINVAL;
+ goto out_get_port;
+ }
+
+ get_random_bytes(nonce, sizeof(nonce));
+
+ /*
+ * "Source device targets the QUERY_STREAM_ENCRYPTION_STATUS message
+ * transaction at the MST Branch device directly connected to the
+ * Source"
+ */
+ txmsg->dst = mgr->mst_primary;
+
+ build_query_stream_enc_status(txmsg, port->vcpi.vcpi, nonce);
+
+ drm_dp_queue_down_tx(mgr, txmsg);
+
+ ret = drm_dp_mst_wait_tx_reply(mgr->mst_primary, txmsg);
+ if (ret < 0) {
+ goto out;
+ } else if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) {
+ drm_dbg_kms(mgr->dev, "query encryption status nak received\n");
+ ret = -ENXIO;
+ goto out;
+ }
+
+ ret = 0;
+ memcpy(status, &txmsg->reply.u.enc_status, sizeof(*status));
+
+ out:
+ drm_dp_mst_topology_put_port(port);
+ out_get_port:
+ kfree(txmsg);
+ return ret;
+ }
+ EXPORT_SYMBOL(drm_dp_send_query_stream_enc_status);
+
+ static int drm_dp_create_payload_step1(struct drm_dp_mst_topology_mgr *mgr,
+ int id,
+ struct drm_dp_payload *payload)
+ {
+ int ret;
+
+ ret = drm_dp_dpcd_write_payload(mgr, id, payload);
+ if (ret < 0) {
+ payload->payload_state = 0;
+ return ret;
+ }
+ payload->payload_state = DP_PAYLOAD_LOCAL;
+ return 0;
+ }
+
+ static int drm_dp_create_payload_step2(struct drm_dp_mst_topology_mgr *mgr,
+ struct drm_dp_mst_port *port,
+ int id,
+ struct drm_dp_payload *payload)
+ {
+ int ret;
+
+ ret = drm_dp_payload_send_msg(mgr, port, id, port->vcpi.pbn);
+ if (ret < 0)
+ return ret;
+ payload->payload_state = DP_PAYLOAD_REMOTE;
+ return ret;
+ }
+
+ static int drm_dp_destroy_payload_step1(struct drm_dp_mst_topology_mgr *mgr,
+ struct drm_dp_mst_port *port,
+ int id,
+ struct drm_dp_payload *payload)
+ {
+ drm_dbg_kms(mgr->dev, "\n");
+ /* it's okay for these to fail */
+ if (port) {
+ drm_dp_payload_send_msg(mgr, port, id, 0);
+ }
+
+ drm_dp_dpcd_write_payload(mgr, id, payload);
+ payload->payload_state = DP_PAYLOAD_DELETE_LOCAL;
+ return 0;
+ }
+
+ static int drm_dp_destroy_payload_step2(struct drm_dp_mst_topology_mgr *mgr,
+ int id,
+ struct drm_dp_payload *payload)
+ {
+ payload->payload_state = 0;
+ return 0;
+ }
+
+ /**
+ * drm_dp_update_payload_part1() - Execute payload update part 1
+ * @mgr: manager to use.
+ * @start_slot: this is the cur slot
+ *
+ * NOTE: start_slot is a temporary workaround for non-atomic drivers,
+ * this will be removed when non-atomic mst helpers are moved out of the helper
+ *
+ * This iterates over all proposed virtual channels, and tries to
+ * allocate space in the link for them. For 0->slots transitions,
+ * this step just writes the VCPI to the MST device. For slots->0
+ * transitions, this writes the updated VCPIs and removes the
+ * remote VC payloads.
+ *
+ * after calling this the driver should generate ACT and payload
+ * packets.
+ */
+ int drm_dp_update_payload_part1(struct drm_dp_mst_topology_mgr *mgr, int start_slot)
+ {
+ struct drm_dp_payload req_payload;
+ struct drm_dp_mst_port *port;
+ int i, j;
+ int cur_slots = start_slot;
+ bool skip;
+
+ mutex_lock(&mgr->payload_lock);
+ for (i = 0; i < mgr->max_payloads; i++) {
+ struct drm_dp_vcpi *vcpi = mgr->proposed_vcpis[i];
+ struct drm_dp_payload *payload = &mgr->payloads[i];
+ bool put_port = false;
+
+ /* solve the current payloads - compare to the hw ones
+ - update the hw view */
+ req_payload.start_slot = cur_slots;
+ if (vcpi) {
+ port = container_of(vcpi, struct drm_dp_mst_port,
+ vcpi);
+
+ mutex_lock(&mgr->lock);
+ skip = !drm_dp_mst_port_downstream_of_branch(port, mgr->mst_primary);
+ mutex_unlock(&mgr->lock);
+
+ if (skip) {
+ drm_dbg_kms(mgr->dev,
+ "Virtual channel %d is not in current topology\n",
+ i);
+ continue;
+ }
+ /* Validated ports don't matter if we're releasing
+ * VCPI
+ */
+ if (vcpi->num_slots) {
+ port = drm_dp_mst_topology_get_port_validated(
+ mgr, port);
+ if (!port) {
+ if (vcpi->num_slots == payload->num_slots) {
+ cur_slots += vcpi->num_slots;
+ payload->start_slot = req_payload.start_slot;
+ continue;
+ } else {
+ drm_dbg_kms(mgr->dev,
+ "Fail:set payload to invalid sink");
+ mutex_unlock(&mgr->payload_lock);
+ return -EINVAL;
+ }
+ }
+ put_port = true;
+ }
+
+ req_payload.num_slots = vcpi->num_slots;
+ req_payload.vcpi = vcpi->vcpi;
+ } else {
+ port = NULL;
+ req_payload.num_slots = 0;
+ }
+
+ payload->start_slot = req_payload.start_slot;
+ /* work out what is required to happen with this payload */
+ if (payload->num_slots != req_payload.num_slots) {
+
+ /* need to push an update for this payload */
+ if (req_payload.num_slots) {
+ drm_dp_create_payload_step1(mgr, vcpi->vcpi,
+ &req_payload);
+ payload->num_slots = req_payload.num_slots;
+ payload->vcpi = req_payload.vcpi;
+
+ } else if (payload->num_slots) {
+ payload->num_slots = 0;
+ drm_dp_destroy_payload_step1(mgr, port,
+ payload->vcpi,
+ payload);
+ req_payload.payload_state =
+ payload->payload_state;
+ payload->start_slot = 0;
+ }
+ payload->payload_state = req_payload.payload_state;
+ }
+ cur_slots += req_payload.num_slots;
+
+ if (put_port)
+ drm_dp_mst_topology_put_port(port);
+ }
+
+ for (i = 0; i < mgr->max_payloads; /* do nothing */) {
+ if (mgr->payloads[i].payload_state != DP_PAYLOAD_DELETE_LOCAL) {
+ i++;
+ continue;
+ }
+
+ drm_dbg_kms(mgr->dev, "removing payload %d\n", i);
+ for (j = i; j < mgr->max_payloads - 1; j++) {
+ mgr->payloads[j] = mgr->payloads[j + 1];
+ mgr->proposed_vcpis[j] = mgr->proposed_vcpis[j + 1];
+
+ if (mgr->proposed_vcpis[j] &&
+ mgr->proposed_vcpis[j]->num_slots) {
+ set_bit(j + 1, &mgr->payload_mask);
+ } else {
+ clear_bit(j + 1, &mgr->payload_mask);
+ }
+ }
+
+ memset(&mgr->payloads[mgr->max_payloads - 1], 0,
+ sizeof(struct drm_dp_payload));
+ mgr->proposed_vcpis[mgr->max_payloads - 1] = NULL;
+ clear_bit(mgr->max_payloads, &mgr->payload_mask);
+ }
+ mutex_unlock(&mgr->payload_lock);
+
+ return 0;
+ }
+ EXPORT_SYMBOL(drm_dp_update_payload_part1);
+
+ /**
+ * drm_dp_update_payload_part2() - Execute payload update part 2
+ * @mgr: manager to use.
+ *
+ * This iterates over all proposed virtual channels, and tries to
+ * allocate space in the link for them. For 0->slots transitions,
+ * this step writes the remote VC payload commands. For slots->0
+ * this just resets some internal state.
+ */
+ int drm_dp_update_payload_part2(struct drm_dp_mst_topology_mgr *mgr)
+ {
+ struct drm_dp_mst_port *port;
+ int i;
+ int ret = 0;
+ bool skip;
+
+ mutex_lock(&mgr->payload_lock);
+ for (i = 0; i < mgr->max_payloads; i++) {
+
+ if (!mgr->proposed_vcpis[i])
+ continue;
+
+ port = container_of(mgr->proposed_vcpis[i], struct drm_dp_mst_port, vcpi);
+
+ mutex_lock(&mgr->lock);
+ skip = !drm_dp_mst_port_downstream_of_branch(port, mgr->mst_primary);
+ mutex_unlock(&mgr->lock);
+
+ if (skip)
+ continue;
+
+ drm_dbg_kms(mgr->dev, "payload %d %d\n", i, mgr->payloads[i].payload_state);
+ if (mgr->payloads[i].payload_state == DP_PAYLOAD_LOCAL) {
+ ret = drm_dp_create_payload_step2(mgr, port, mgr->proposed_vcpis[i]->vcpi, &mgr->payloads[i]);
+ } else if (mgr->payloads[i].payload_state == DP_PAYLOAD_DELETE_LOCAL) {
+ ret = drm_dp_destroy_payload_step2(mgr, mgr->proposed_vcpis[i]->vcpi, &mgr->payloads[i]);
+ }
+ if (ret) {
+ mutex_unlock(&mgr->payload_lock);
+ return ret;
+ }
+ }
+ mutex_unlock(&mgr->payload_lock);
+ return 0;
+ }
+ EXPORT_SYMBOL(drm_dp_update_payload_part2);
+
+ static int drm_dp_send_dpcd_read(struct drm_dp_mst_topology_mgr *mgr,
+ struct drm_dp_mst_port *port,
+ int offset, int size, u8 *bytes)
+ {
+ int ret = 0;
+ struct drm_dp_sideband_msg_tx *txmsg;
+ struct drm_dp_mst_branch *mstb;
+
+ mstb = drm_dp_mst_topology_get_mstb_validated(mgr, port->parent);
+ if (!mstb)
+ return -EINVAL;
+
+ txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL);
+ if (!txmsg) {
+ ret = -ENOMEM;
+ goto fail_put;
+ }
+
+ build_dpcd_read(txmsg, port->port_num, offset, size);
+ txmsg->dst = port->parent;
+
+ drm_dp_queue_down_tx(mgr, txmsg);
+
+ ret = drm_dp_mst_wait_tx_reply(mstb, txmsg);
+ if (ret < 0)
+ goto fail_free;
+
+ /* DPCD read should never be NACKed */
+ if (txmsg->reply.reply_type == 1) {
+ drm_err(mgr->dev, "mstb %p port %d: DPCD read on addr 0x%x for %d bytes NAKed\n",
+ mstb, port->port_num, offset, size);
+ ret = -EIO;
+ goto fail_free;
+ }
+
+ if (txmsg->reply.u.remote_dpcd_read_ack.num_bytes != size) {
+ ret = -EPROTO;
+ goto fail_free;
+ }
+
+ ret = min_t(size_t, txmsg->reply.u.remote_dpcd_read_ack.num_bytes,
+ size);
+ memcpy(bytes, txmsg->reply.u.remote_dpcd_read_ack.bytes, ret);
+
+ fail_free:
+ kfree(txmsg);
+ fail_put:
+ drm_dp_mst_topology_put_mstb(mstb);
+
+ return ret;
+ }
+
+ static int drm_dp_send_dpcd_write(struct drm_dp_mst_topology_mgr *mgr,
+ struct drm_dp_mst_port *port,
+ int offset, int size, u8 *bytes)
+ {
+ int ret;
+ struct drm_dp_sideband_msg_tx *txmsg;
+ struct drm_dp_mst_branch *mstb;
+
+ mstb = drm_dp_mst_topology_get_mstb_validated(mgr, port->parent);
+ if (!mstb)
+ return -EINVAL;
+
+ txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL);
+ if (!txmsg) {
+ ret = -ENOMEM;
+ goto fail_put;
+ }
+
+ build_dpcd_write(txmsg, port->port_num, offset, size, bytes);
+ txmsg->dst = mstb;
+
+ drm_dp_queue_down_tx(mgr, txmsg);
+
+ ret = drm_dp_mst_wait_tx_reply(mstb, txmsg);
+ if (ret > 0) {
+ if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK)
+ ret = -EIO;
+ else
+ ret = size;
+ }
+
+ kfree(txmsg);
+ fail_put:
+ drm_dp_mst_topology_put_mstb(mstb);
+ return ret;
+ }
+
+ static int drm_dp_encode_up_ack_reply(struct drm_dp_sideband_msg_tx *msg, u8 req_type)
+ {
+ struct drm_dp_sideband_msg_reply_body reply;
+
+ reply.reply_type = DP_SIDEBAND_REPLY_ACK;
+ reply.req_type = req_type;
+ drm_dp_encode_sideband_reply(&reply, msg);
+ return 0;
+ }
+
+ static int drm_dp_send_up_ack_reply(struct drm_dp_mst_topology_mgr *mgr,
+ struct drm_dp_mst_branch *mstb,
+ int req_type, bool broadcast)
+ {
+ struct drm_dp_sideband_msg_tx *txmsg;
+
+ txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL);
+ if (!txmsg)
+ return -ENOMEM;
+
+ txmsg->dst = mstb;
+ drm_dp_encode_up_ack_reply(txmsg, req_type);
+
+ mutex_lock(&mgr->qlock);
+ /* construct a chunk from the first msg in the tx_msg queue */
+ process_single_tx_qlock(mgr, txmsg, true);
+ mutex_unlock(&mgr->qlock);
+
+ kfree(txmsg);
+ return 0;
+ }
+
+ /**
+ * drm_dp_get_vc_payload_bw - get the VC payload BW for an MST link
+ * @mgr: The &drm_dp_mst_topology_mgr to use
+ * @link_rate: link rate in 10kbits/s units
+ * @link_lane_count: lane count
+ *
+ * Calculate the total bandwidth of a MultiStream Transport link. The returned
+ * value is in units of PBNs/(timeslots/1 MTP). This value can be used to
+ * convert the number of PBNs required for a given stream to the number of
+ * timeslots this stream requires in each MTP.
+ */
+ int drm_dp_get_vc_payload_bw(const struct drm_dp_mst_topology_mgr *mgr,
+ int link_rate, int link_lane_count)
+ {
+ if (link_rate == 0 || link_lane_count == 0)
+ drm_dbg_kms(mgr->dev, "invalid link rate/lane count: (%d / %d)\n",
+ link_rate, link_lane_count);
+
+ /* See DP v2.0 2.6.4.2, VCPayload_Bandwidth_for_OneTimeSlotPer_MTP_Allocation */
+ return link_rate * link_lane_count / 54000;
+ }
+ EXPORT_SYMBOL(drm_dp_get_vc_payload_bw);
+
+ /**
+ * drm_dp_read_mst_cap() - check whether or not a sink supports MST
+ * @aux: The DP AUX channel to use
+ * @dpcd: A cached copy of the DPCD capabilities for this sink
+ *
+ * Returns: %True if the sink supports MST, %false otherwise
+ */
+ bool drm_dp_read_mst_cap(struct drm_dp_aux *aux,
+ const u8 dpcd[DP_RECEIVER_CAP_SIZE])
+ {
+ u8 mstm_cap;
+
+ if (dpcd[DP_DPCD_REV] < DP_DPCD_REV_12)
+ return false;
+
+ if (drm_dp_dpcd_readb(aux, DP_MSTM_CAP, &mstm_cap) != 1)
+ return false;
+
+ return mstm_cap & DP_MST_CAP;
+ }
+ EXPORT_SYMBOL(drm_dp_read_mst_cap);
+
+ /**
+ * drm_dp_mst_topology_mgr_set_mst() - Set the MST state for a topology manager
+ * @mgr: manager to set state for
+ * @mst_state: true to enable MST on this connector - false to disable.
+ *
+ * This is called by the driver when it detects an MST capable device plugged
+ * into a DP MST capable port, or when a DP MST capable device is unplugged.
+ */
+ int drm_dp_mst_topology_mgr_set_mst(struct drm_dp_mst_topology_mgr *mgr, bool mst_state)
+ {
+ int ret = 0;
+ struct drm_dp_mst_branch *mstb = NULL;
+
+ mutex_lock(&mgr->payload_lock);
+ mutex_lock(&mgr->lock);
+ if (mst_state == mgr->mst_state)
+ goto out_unlock;
+
+ mgr->mst_state = mst_state;
+ /* set the device into MST mode */
+ if (mst_state) {
+ struct drm_dp_payload reset_pay;
+ int lane_count;
+ int link_rate;
+
+ WARN_ON(mgr->mst_primary);
+
+ /* get dpcd info */
+ ret = drm_dp_read_dpcd_caps(mgr->aux, mgr->dpcd);
+ if (ret < 0) {
+ drm_dbg_kms(mgr->dev, "%s: failed to read DPCD, ret %d\n",
+ mgr->aux->name, ret);
+ goto out_unlock;
+ }
+
+ lane_count = min_t(int, mgr->dpcd[2] & DP_MAX_LANE_COUNT_MASK, mgr->max_lane_count);
+ link_rate = min_t(int, drm_dp_bw_code_to_link_rate(mgr->dpcd[1]), mgr->max_link_rate);
+ mgr->pbn_div = drm_dp_get_vc_payload_bw(mgr,
+ link_rate,
+ lane_count);
+ if (mgr->pbn_div == 0) {
+ ret = -EINVAL;
+ goto out_unlock;
+ }
+
+ /* add initial branch device at LCT 1 */
+ mstb = drm_dp_add_mst_branch_device(1, NULL);
+ if (mstb == NULL) {
+ ret = -ENOMEM;
+ goto out_unlock;
+ }
+ mstb->mgr = mgr;
+
+ /* give this the main reference */
+ mgr->mst_primary = mstb;
+ drm_dp_mst_topology_get_mstb(mgr->mst_primary);
+
+ ret = drm_dp_dpcd_writeb(mgr->aux, DP_MSTM_CTRL,
+ DP_MST_EN |
+ DP_UP_REQ_EN |
+ DP_UPSTREAM_IS_SRC);
+ if (ret < 0)
+ goto out_unlock;
+
+ reset_pay.start_slot = 0;
+ reset_pay.num_slots = 0x3f;
+ drm_dp_dpcd_write_payload(mgr, 0, &reset_pay);
+
+ queue_work(system_long_wq, &mgr->work);
+
+ ret = 0;
+ } else {
+ /* disable MST on the device */
+ mstb = mgr->mst_primary;
+ mgr->mst_primary = NULL;
+ /* this can fail if the device is gone */
+ drm_dp_dpcd_writeb(mgr->aux, DP_MSTM_CTRL, 0);
+ ret = 0;
+ memset(mgr->payloads, 0,
+ mgr->max_payloads * sizeof(mgr->payloads[0]));
+ memset(mgr->proposed_vcpis, 0,
+ mgr->max_payloads * sizeof(mgr->proposed_vcpis[0]));
+ mgr->payload_mask = 0;
+ set_bit(0, &mgr->payload_mask);
+ mgr->vcpi_mask = 0;
+ mgr->payload_id_table_cleared = false;
+ }
+
+ out_unlock:
+ mutex_unlock(&mgr->lock);
+ mutex_unlock(&mgr->payload_lock);
+ if (mstb)
+ drm_dp_mst_topology_put_mstb(mstb);
+ return ret;
+
+ }
+ EXPORT_SYMBOL(drm_dp_mst_topology_mgr_set_mst);
+
+ static void
+ drm_dp_mst_topology_mgr_invalidate_mstb(struct drm_dp_mst_branch *mstb)
+ {
+ struct drm_dp_mst_port *port;
+
+ /* The link address will need to be re-sent on resume */
+ mstb->link_address_sent = false;
+
+ list_for_each_entry(port, &mstb->ports, next)
+ if (port->mstb)
+ drm_dp_mst_topology_mgr_invalidate_mstb(port->mstb);
+ }
+
+ /**
+ * drm_dp_mst_topology_mgr_suspend() - suspend the MST manager
+ * @mgr: manager to suspend
+ *
+ * This function tells the MST device that we can't handle UP messages
+ * anymore. This should stop it from sending any since we are suspended.
+ */
+ void drm_dp_mst_topology_mgr_suspend(struct drm_dp_mst_topology_mgr *mgr)
+ {
+ mutex_lock(&mgr->lock);
+ drm_dp_dpcd_writeb(mgr->aux, DP_MSTM_CTRL,
+ DP_MST_EN | DP_UPSTREAM_IS_SRC);
+ mutex_unlock(&mgr->lock);
+ flush_work(&mgr->up_req_work);
+ flush_work(&mgr->work);
+ flush_work(&mgr->delayed_destroy_work);
+
+ mutex_lock(&mgr->lock);
+ if (mgr->mst_state && mgr->mst_primary)
+ drm_dp_mst_topology_mgr_invalidate_mstb(mgr->mst_primary);
+ mutex_unlock(&mgr->lock);
+ }
+ EXPORT_SYMBOL(drm_dp_mst_topology_mgr_suspend);
+
+ /**
+ * drm_dp_mst_topology_mgr_resume() - resume the MST manager
+ * @mgr: manager to resume
+ * @sync: whether or not to perform topology reprobing synchronously
+ *
+ * This will fetch DPCD and see if the device is still there,
+ * if it is, it will rewrite the MSTM control bits, and return.
+ *
+ * If the device fails this returns -1, and the driver should do
+ * a full MST reprobe, in case we were undocked.
+ *
+ * During system resume (where it is assumed that the driver will be calling
+ * drm_atomic_helper_resume()) this function should be called beforehand with
+ * @sync set to true. In contexts like runtime resume where the driver is not
+ * expected to be calling drm_atomic_helper_resume(), this function should be
+ * called with @sync set to false in order to avoid deadlocking.
+ *
+ * Returns: -1 if the MST topology was removed while we were suspended, 0
+ * otherwise.
+ */
+ int drm_dp_mst_topology_mgr_resume(struct drm_dp_mst_topology_mgr *mgr,
+ bool sync)
+ {
+ int ret;
+ u8 guid[16];
+
+ mutex_lock(&mgr->lock);
+ if (!mgr->mst_primary)
+ goto out_fail;
+
+ ret = drm_dp_dpcd_read(mgr->aux, DP_DPCD_REV, mgr->dpcd,
+ DP_RECEIVER_CAP_SIZE);
+ if (ret != DP_RECEIVER_CAP_SIZE) {
+ drm_dbg_kms(mgr->dev, "dpcd read failed - undocked during suspend?\n");
+ goto out_fail;
+ }
+
+ ret = drm_dp_dpcd_writeb(mgr->aux, DP_MSTM_CTRL,
+ DP_MST_EN |
+ DP_UP_REQ_EN |
+ DP_UPSTREAM_IS_SRC);
+ if (ret < 0) {
+ drm_dbg_kms(mgr->dev, "mst write failed - undocked during suspend?\n");
+ goto out_fail;
+ }
+
+ /* Some hubs forget their guids after they resume */
+ ret = drm_dp_dpcd_read(mgr->aux, DP_GUID, guid, 16);
+ if (ret != 16) {
+ drm_dbg_kms(mgr->dev, "dpcd read failed - undocked during suspend?\n");
+ goto out_fail;
+ }
+
+ ret = drm_dp_check_mstb_guid(mgr->mst_primary, guid);
+ if (ret) {
+ drm_dbg_kms(mgr->dev, "check mstb failed - undocked during suspend?\n");
+ goto out_fail;
+ }
+
+ /*
+ * For the final step of resuming the topology, we need to bring the
+ * state of our in-memory topology back into sync with reality. So,
+ * restart the probing process as if we're probing a new hub
+ */
+ queue_work(system_long_wq, &mgr->work);
+ mutex_unlock(&mgr->lock);
+
+ if (sync) {
+ drm_dbg_kms(mgr->dev,
+ "Waiting for link probe work to finish re-syncing topology...\n");
+ flush_work(&mgr->work);
+ }
+
+ return 0;
+
+ out_fail:
+ mutex_unlock(&mgr->lock);
+ return -1;
+ }
+ EXPORT_SYMBOL(drm_dp_mst_topology_mgr_resume);
+
+ static bool
+ drm_dp_get_one_sb_msg(struct drm_dp_mst_topology_mgr *mgr, bool up,
+ struct drm_dp_mst_branch **mstb)
+ {
+ int len;
+ u8 replyblock[32];
+ int replylen, curreply;
+ int ret;
+ u8 hdrlen;
+ struct drm_dp_sideband_msg_hdr hdr;
+ struct drm_dp_sideband_msg_rx *msg =
+ up ? &mgr->up_req_recv : &mgr->down_rep_recv;
+ int basereg = up ? DP_SIDEBAND_MSG_UP_REQ_BASE :
+ DP_SIDEBAND_MSG_DOWN_REP_BASE;
+
+ if (!up)
+ *mstb = NULL;
+
+ len = min(mgr->max_dpcd_transaction_bytes, 16);
+ ret = drm_dp_dpcd_read(mgr->aux, basereg, replyblock, len);
+ if (ret != len) {
+ drm_dbg_kms(mgr->dev, "failed to read DPCD down rep %d %d\n", len, ret);
+ return false;
+ }
+
+ ret = drm_dp_decode_sideband_msg_hdr(mgr, &hdr, replyblock, len, &hdrlen);
+ if (ret == false) {
+ print_hex_dump(KERN_DEBUG, "failed hdr", DUMP_PREFIX_NONE, 16,
+ 1, replyblock, len, false);
+ drm_dbg_kms(mgr->dev, "ERROR: failed header\n");
+ return false;
+ }
+
+ if (!up) {
+ /* Caller is responsible for giving back this reference */
+ *mstb = drm_dp_get_mst_branch_device(mgr, hdr.lct, hdr.rad);
+ if (!*mstb) {
+ drm_dbg_kms(mgr->dev, "Got MST reply from unknown device %d\n", hdr.lct);
+ return false;
+ }
+ }
+
+ if (!drm_dp_sideband_msg_set_header(msg, &hdr, hdrlen)) {
+ drm_dbg_kms(mgr->dev, "sideband msg set header failed %d\n", replyblock[0]);
+ return false;
+ }
+
+ replylen = min(msg->curchunk_len, (u8)(len - hdrlen));
+ ret = drm_dp_sideband_append_payload(msg, replyblock + hdrlen, replylen);
+ if (!ret) {
+ drm_dbg_kms(mgr->dev, "sideband msg build failed %d\n", replyblock[0]);
+ return false;
+ }
+
+ replylen = msg->curchunk_len + msg->curchunk_hdrlen - len;
+ curreply = len;
+ while (replylen > 0) {
+ len = min3(replylen, mgr->max_dpcd_transaction_bytes, 16);
+ ret = drm_dp_dpcd_read(mgr->aux, basereg + curreply,
+ replyblock, len);
+ if (ret != len) {
+ drm_dbg_kms(mgr->dev, "failed to read a chunk (len %d, ret %d)\n",
+ len, ret);
+ return false;
+ }
+
+ ret = drm_dp_sideband_append_payload(msg, replyblock, len);
+ if (!ret) {
+ drm_dbg_kms(mgr->dev, "failed to build sideband msg\n");
+ return false;
+ }
+
+ curreply += len;
+ replylen -= len;
+ }
+ return true;
+ }
+
+ static int drm_dp_mst_handle_down_rep(struct drm_dp_mst_topology_mgr *mgr)
+ {
+ struct drm_dp_sideband_msg_tx *txmsg;
+ struct drm_dp_mst_branch *mstb = NULL;
+ struct drm_dp_sideband_msg_rx *msg = &mgr->down_rep_recv;
+
+ if (!drm_dp_get_one_sb_msg(mgr, false, &mstb))
+ goto out;
+
+ /* Multi-packet message transmission, don't clear the reply */
+ if (!msg->have_eomt)
+ goto out;
+
+ /* find the message */
+ mutex_lock(&mgr->qlock);
+ txmsg = list_first_entry_or_null(&mgr->tx_msg_downq,
+ struct drm_dp_sideband_msg_tx, next);
+ mutex_unlock(&mgr->qlock);
+
+ /* Were we actually expecting a response, and from this mstb? */
+ if (!txmsg || txmsg->dst != mstb) {
+ struct drm_dp_sideband_msg_hdr *hdr;
+
+ hdr = &msg->initial_hdr;
+ drm_dbg_kms(mgr->dev, "Got MST reply with no msg %p %d %d %02x %02x\n",
+ mstb, hdr->seqno, hdr->lct, hdr->rad[0], msg->msg[0]);
+ goto out_clear_reply;
+ }
+
+ drm_dp_sideband_parse_reply(mgr, msg, &txmsg->reply);
+
+ if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) {
+ drm_dbg_kms(mgr->dev,
+ "Got NAK reply: req 0x%02x (%s), reason 0x%02x (%s), nak data 0x%02x\n",
+ txmsg->reply.req_type,
+ drm_dp_mst_req_type_str(txmsg->reply.req_type),
+ txmsg->reply.u.nak.reason,
+ drm_dp_mst_nak_reason_str(txmsg->reply.u.nak.reason),
+ txmsg->reply.u.nak.nak_data);
+ }
+
+ memset(msg, 0, sizeof(struct drm_dp_sideband_msg_rx));
+ drm_dp_mst_topology_put_mstb(mstb);
+
+ mutex_lock(&mgr->qlock);
+ txmsg->state = DRM_DP_SIDEBAND_TX_RX;
+ list_del(&txmsg->next);
+ mutex_unlock(&mgr->qlock);
+
+ wake_up_all(&mgr->tx_waitq);
+
+ return 0;
+
+ out_clear_reply:
+ memset(msg, 0, sizeof(struct drm_dp_sideband_msg_rx));
+ out:
+ if (mstb)
+ drm_dp_mst_topology_put_mstb(mstb);
+
+ return 0;
+ }
+
+ static inline bool
+ drm_dp_mst_process_up_req(struct drm_dp_mst_topology_mgr *mgr,
+ struct drm_dp_pending_up_req *up_req)
+ {
+ struct drm_dp_mst_branch *mstb = NULL;
+ struct drm_dp_sideband_msg_req_body *msg = &up_req->msg;
+ struct drm_dp_sideband_msg_hdr *hdr = &up_req->hdr;
+ bool hotplug = false;
+
+ if (hdr->broadcast) {
+ const u8 *guid = NULL;
+
+ if (msg->req_type == DP_CONNECTION_STATUS_NOTIFY)
+ guid = msg->u.conn_stat.guid;
+ else if (msg->req_type == DP_RESOURCE_STATUS_NOTIFY)
+ guid = msg->u.resource_stat.guid;
+
+ if (guid)
+ mstb = drm_dp_get_mst_branch_device_by_guid(mgr, guid);
+ } else {
+ mstb = drm_dp_get_mst_branch_device(mgr, hdr->lct, hdr->rad);
+ }
+
+ if (!mstb) {
+ drm_dbg_kms(mgr->dev, "Got MST reply from unknown device %d\n", hdr->lct);
+ return false;
+ }
+
+ /* TODO: Add missing handler for DP_RESOURCE_STATUS_NOTIFY events */
+ if (msg->req_type == DP_CONNECTION_STATUS_NOTIFY) {
+ drm_dp_mst_handle_conn_stat(mstb, &msg->u.conn_stat);
+ hotplug = true;
+ }
+
+ drm_dp_mst_topology_put_mstb(mstb);
+ return hotplug;
+ }
+
+ static void drm_dp_mst_up_req_work(struct work_struct *work)
+ {
+ struct drm_dp_mst_topology_mgr *mgr =
+ container_of(work, struct drm_dp_mst_topology_mgr,
+ up_req_work);
+ struct drm_dp_pending_up_req *up_req;
+ bool send_hotplug = false;
+
+ mutex_lock(&mgr->probe_lock);
+ while (true) {
+ mutex_lock(&mgr->up_req_lock);
+ up_req = list_first_entry_or_null(&mgr->up_req_list,
+ struct drm_dp_pending_up_req,
+ next);
+ if (up_req)
+ list_del(&up_req->next);
+ mutex_unlock(&mgr->up_req_lock);
+
+ if (!up_req)
+ break;
+
+ send_hotplug |= drm_dp_mst_process_up_req(mgr, up_req);
+ kfree(up_req);
+ }
+ mutex_unlock(&mgr->probe_lock);
+
+ if (send_hotplug)
+ drm_kms_helper_hotplug_event(mgr->dev);
+ }
+
+ static int drm_dp_mst_handle_up_req(struct drm_dp_mst_topology_mgr *mgr)
+ {
+ struct drm_dp_pending_up_req *up_req;
+
+ if (!drm_dp_get_one_sb_msg(mgr, true, NULL))
+ goto out;
+
+ if (!mgr->up_req_recv.have_eomt)
+ return 0;
+
+ up_req = kzalloc(sizeof(*up_req), GFP_KERNEL);
+ if (!up_req)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&up_req->next);
+
+ drm_dp_sideband_parse_req(mgr, &mgr->up_req_recv, &up_req->msg);
+
+ if (up_req->msg.req_type != DP_CONNECTION_STATUS_NOTIFY &&
+ up_req->msg.req_type != DP_RESOURCE_STATUS_NOTIFY) {
+ drm_dbg_kms(mgr->dev, "Received unknown up req type, ignoring: %x\n",
+ up_req->msg.req_type);
+ kfree(up_req);
+ goto out;
+ }
+
+ drm_dp_send_up_ack_reply(mgr, mgr->mst_primary, up_req->msg.req_type,
+ false);
+
+ if (up_req->msg.req_type == DP_CONNECTION_STATUS_NOTIFY) {
+ const struct drm_dp_connection_status_notify *conn_stat =
+ &up_req->msg.u.conn_stat;
+
+ drm_dbg_kms(mgr->dev, "Got CSN: pn: %d ldps:%d ddps: %d mcs: %d ip: %d pdt: %d\n",
+ conn_stat->port_number,
+ conn_stat->legacy_device_plug_status,
+ conn_stat->displayport_device_plug_status,
+ conn_stat->message_capability_status,
+ conn_stat->input_port,
+ conn_stat->peer_device_type);
+ } else if (up_req->msg.req_type == DP_RESOURCE_STATUS_NOTIFY) {
+ const struct drm_dp_resource_status_notify *res_stat =
+ &up_req->msg.u.resource_stat;
+
+ drm_dbg_kms(mgr->dev, "Got RSN: pn: %d avail_pbn %d\n",
+ res_stat->port_number,
+ res_stat->available_pbn);
+ }
+
+ up_req->hdr = mgr->up_req_recv.initial_hdr;
+ mutex_lock(&mgr->up_req_lock);
+ list_add_tail(&up_req->next, &mgr->up_req_list);
+ mutex_unlock(&mgr->up_req_lock);
+ queue_work(system_long_wq, &mgr->up_req_work);
+
+ out:
+ memset(&mgr->up_req_recv, 0, sizeof(struct drm_dp_sideband_msg_rx));
+ return 0;
+ }
+
+ /**
+ * drm_dp_mst_hpd_irq() - MST hotplug IRQ notify
+ * @mgr: manager to notify irq for.
+ * @esi: 4 bytes from SINK_COUNT_ESI
+ * @handled: whether the hpd interrupt was consumed or not
+ *
+ * This should be called from the driver when it detects a short IRQ,
+ * along with the value of the DEVICE_SERVICE_IRQ_VECTOR_ESI0. The
+ * topology manager will process the sideband messages received as a result
+ * of this.
+ */
+ int drm_dp_mst_hpd_irq(struct drm_dp_mst_topology_mgr *mgr, u8 *esi, bool *handled)
+ {
+ int ret = 0;
+ int sc;
+ *handled = false;
+ sc = DP_GET_SINK_COUNT(esi[0]);
+
+ if (sc != mgr->sink_count) {
+ mgr->sink_count = sc;
+ *handled = true;
+ }
+
+ if (esi[1] & DP_DOWN_REP_MSG_RDY) {
+ ret = drm_dp_mst_handle_down_rep(mgr);
+ *handled = true;
+ }
+
+ if (esi[1] & DP_UP_REQ_MSG_RDY) {
+ ret |= drm_dp_mst_handle_up_req(mgr);
+ *handled = true;
+ }
+
+ drm_dp_mst_kick_tx(mgr);
+ return ret;
+ }
+ EXPORT_SYMBOL(drm_dp_mst_hpd_irq);
+
+ /**
+ * drm_dp_mst_detect_port() - get connection status for an MST port
+ * @connector: DRM connector for this port
+ * @ctx: The acquisition context to use for grabbing locks
+ * @mgr: manager for this port
+ * @port: pointer to a port
+ *
+ * This returns the current connection state for a port.
+ */
+ int
+ drm_dp_mst_detect_port(struct drm_connector *connector,
+ struct drm_modeset_acquire_ctx *ctx,
+ struct drm_dp_mst_topology_mgr *mgr,
+ struct drm_dp_mst_port *port)
+ {
+ int ret;
+
+ /* we need to search for the port in the mgr in case it's gone */
+ port = drm_dp_mst_topology_get_port_validated(mgr, port);
+ if (!port)
+ return connector_status_disconnected;
+
+ ret = drm_modeset_lock(&mgr->base.lock, ctx);
+ if (ret)
+ goto out;
+
+ ret = connector_status_disconnected;
+
+ if (!port->ddps)
+ goto out;
+
+ switch (port->pdt) {
+ case DP_PEER_DEVICE_NONE:
+ break;
+ case DP_PEER_DEVICE_MST_BRANCHING:
+ if (!port->mcs)
+ ret = connector_status_connected;
+ break;
+
+ case DP_PEER_DEVICE_SST_SINK:
+ ret = connector_status_connected;
+ /* for logical ports - cache the EDID */
+ if (port->port_num >= DP_MST_LOGICAL_PORT_0 && !port->cached_edid)
+ port->cached_edid = drm_get_edid(connector, &port->aux.ddc);
+ break;
+ case DP_PEER_DEVICE_DP_LEGACY_CONV:
+ if (port->ldps)
+ ret = connector_status_connected;
+ break;
+ }
+ out:
+ drm_dp_mst_topology_put_port(port);
+ return ret;
+ }
+ EXPORT_SYMBOL(drm_dp_mst_detect_port);
+
+ /**
+ * drm_dp_mst_get_edid() - get EDID for an MST port
+ * @connector: toplevel connector to get EDID for
+ * @mgr: manager for this port
+ * @port: unverified pointer to a port.
+ *
+ * This returns an EDID for the port connected to a connector,
+ * It validates the pointer still exists so the caller doesn't require a
+ * reference.
+ */
+ struct edid *drm_dp_mst_get_edid(struct drm_connector *connector, struct drm_dp_mst_topology_mgr *mgr, struct drm_dp_mst_port *port)
+ {
+ struct edid *edid = NULL;
+
+ /* we need to search for the port in the mgr in case it's gone */
+ port = drm_dp_mst_topology_get_port_validated(mgr, port);
+ if (!port)
+ return NULL;
+
+ if (port->cached_edid)
+ edid = drm_edid_duplicate(port->cached_edid);
+ else {
+ edid = drm_get_edid(connector, &port->aux.ddc);
+ }
+ port->has_audio = drm_detect_monitor_audio(edid);
+ drm_dp_mst_topology_put_port(port);
+ return edid;
+ }
+ EXPORT_SYMBOL(drm_dp_mst_get_edid);
+
+ /**
+ * drm_dp_find_vcpi_slots() - Find VCPI slots for this PBN value
+ * @mgr: manager to use
+ * @pbn: payload bandwidth to convert into slots.
+ *
+ * Calculate the number of VCPI slots that will be required for the given PBN
+ * value. This function is deprecated, and should not be used in atomic
+ * drivers.
+ *
+ * RETURNS:
+ * The total slots required for this port, or error.
+ */
+ int drm_dp_find_vcpi_slots(struct drm_dp_mst_topology_mgr *mgr,
+ int pbn)
+ {
+ int num_slots;
+
+ num_slots = DIV_ROUND_UP(pbn, mgr->pbn_div);
+
+ /* max. time slots - one slot for MTP header */
+ if (num_slots > 63)
+ return -ENOSPC;
+ return num_slots;
+ }
+ EXPORT_SYMBOL(drm_dp_find_vcpi_slots);
+
+ static int drm_dp_init_vcpi(struct drm_dp_mst_topology_mgr *mgr,
+ struct drm_dp_vcpi *vcpi, int pbn, int slots)
+ {
+ int ret;
+
+ vcpi->pbn = pbn;
+ vcpi->aligned_pbn = slots * mgr->pbn_div;
+ vcpi->num_slots = slots;
+
+ ret = drm_dp_mst_assign_payload_id(mgr, vcpi);
+ if (ret < 0)
+ return ret;
+ return 0;
+ }
+
+ /**
+ * drm_dp_atomic_find_vcpi_slots() - Find and add VCPI slots to the state
+ * @state: global atomic state
+ * @mgr: MST topology manager for the port
+ * @port: port to find vcpi slots for
+ * @pbn: bandwidth required for the mode in PBN
+ * @pbn_div: divider for DSC mode that takes FEC into account
+ *
+ * Allocates VCPI slots to @port, replacing any previous VCPI allocations it
+ * may have had. Any atomic drivers which support MST must call this function
+ * in their &drm_encoder_helper_funcs.atomic_check() callback to change the
+ * current VCPI allocation for the new state, but only when
+ * &drm_crtc_state.mode_changed or &drm_crtc_state.connectors_changed is set
+ * to ensure compatibility with userspace applications that still use the
+ * legacy modesetting UAPI.
+ *
+ * Allocations set by this function are not checked against the bandwidth
+ * restraints of @mgr until the driver calls drm_dp_mst_atomic_check().
+ *
+ * Additionally, it is OK to call this function multiple times on the same
+ * @port as needed. It is not OK however, to call this function and
+ * drm_dp_atomic_release_vcpi_slots() in the same atomic check phase.
+ *
+ * See also:
+ * drm_dp_atomic_release_vcpi_slots()
+ * drm_dp_mst_atomic_check()
+ *
+ * Returns:
+ * Total slots in the atomic state assigned for this port, or a negative error
+ * code if the port no longer exists
+ */
+ int drm_dp_atomic_find_vcpi_slots(struct drm_atomic_state *state,
+ struct drm_dp_mst_topology_mgr *mgr,
+ struct drm_dp_mst_port *port, int pbn,
+ int pbn_div)
+ {
+ struct drm_dp_mst_topology_state *topology_state;
+ struct drm_dp_vcpi_allocation *pos, *vcpi = NULL;
+ int prev_slots, prev_bw, req_slots;
+
+ topology_state = drm_atomic_get_mst_topology_state(state, mgr);
+ if (IS_ERR(topology_state))
+ return PTR_ERR(topology_state);
+
+ /* Find the current allocation for this port, if any */
+ list_for_each_entry(pos, &topology_state->vcpis, next) {
+ if (pos->port == port) {
+ vcpi = pos;
+ prev_slots = vcpi->vcpi;
+ prev_bw = vcpi->pbn;
+
+ /*
+ * This should never happen, unless the driver tries
+ * releasing and allocating the same VCPI allocation,
+ * which is an error
+ */
+ if (WARN_ON(!prev_slots)) {
+ drm_err(mgr->dev,
+ "cannot allocate and release VCPI on [MST PORT:%p] in the same state\n",
+ port);
+ return -EINVAL;
+ }
+
+ break;
+ }
+ }
+ if (!vcpi) {
+ prev_slots = 0;
+ prev_bw = 0;
+ }
+
+ if (pbn_div <= 0)
+ pbn_div = mgr->pbn_div;
+
+ req_slots = DIV_ROUND_UP(pbn, pbn_div);
+
+ drm_dbg_atomic(mgr->dev, "[CONNECTOR:%d:%s] [MST PORT:%p] VCPI %d -> %d\n",
+ port->connector->base.id, port->connector->name,
+ port, prev_slots, req_slots);
+ drm_dbg_atomic(mgr->dev, "[CONNECTOR:%d:%s] [MST PORT:%p] PBN %d -> %d\n",
+ port->connector->base.id, port->connector->name,
+ port, prev_bw, pbn);
+
+ /* Add the new allocation to the state */
+ if (!vcpi) {
+ vcpi = kzalloc(sizeof(*vcpi), GFP_KERNEL);
+ if (!vcpi)
+ return -ENOMEM;
+
+ drm_dp_mst_get_port_malloc(port);
+ vcpi->port = port;
+ list_add(&vcpi->next, &topology_state->vcpis);
+ }
+ vcpi->vcpi = req_slots;
+ vcpi->pbn = pbn;
+
+ return req_slots;
+ }
+ EXPORT_SYMBOL(drm_dp_atomic_find_vcpi_slots);
+
+ /**
+ * drm_dp_atomic_release_vcpi_slots() - Release allocated vcpi slots
+ * @state: global atomic state
+ * @mgr: MST topology manager for the port
+ * @port: The port to release the VCPI slots from
+ *
+ * Releases any VCPI slots that have been allocated to a port in the atomic
+ * state. Any atomic drivers which support MST must call this function in
+ * their &drm_connector_helper_funcs.atomic_check() callback when the
+ * connector will no longer have VCPI allocated (e.g. because its CRTC was
+ * removed) when it had VCPI allocated in the previous atomic state.
+ *
+ * It is OK to call this even if @port has been removed from the system.
+ * Additionally, it is OK to call this function multiple times on the same
+ * @port as needed. It is not OK however, to call this function and
+ * drm_dp_atomic_find_vcpi_slots() on the same @port in a single atomic check
+ * phase.
+ *
+ * See also:
+ * drm_dp_atomic_find_vcpi_slots()
+ * drm_dp_mst_atomic_check()
+ *
+ * Returns:
+ * 0 if all slots for this port were added back to
+ * &drm_dp_mst_topology_state.avail_slots or negative error code
+ */
+ int drm_dp_atomic_release_vcpi_slots(struct drm_atomic_state *state,
+ struct drm_dp_mst_topology_mgr *mgr,
+ struct drm_dp_mst_port *port)
+ {
+ struct drm_dp_mst_topology_state *topology_state;
+ struct drm_dp_vcpi_allocation *pos;
+ bool found = false;
+
+ topology_state = drm_atomic_get_mst_topology_state(state, mgr);
+ if (IS_ERR(topology_state))
+ return PTR_ERR(topology_state);
+
+ list_for_each_entry(pos, &topology_state->vcpis, next) {
+ if (pos->port == port) {
+ found = true;
+ break;
+ }
+ }
+ if (WARN_ON(!found)) {
+ drm_err(mgr->dev, "no VCPI for [MST PORT:%p] found in mst state %p\n",
+ port, &topology_state->base);
+ return -EINVAL;
+ }
+
+ drm_dbg_atomic(mgr->dev, "[MST PORT:%p] VCPI %d -> 0\n", port, pos->vcpi);
+ if (pos->vcpi) {
+ drm_dp_mst_put_port_malloc(port);
+ pos->vcpi = 0;
+ pos->pbn = 0;
+ }
+
+ return 0;
+ }
+ EXPORT_SYMBOL(drm_dp_atomic_release_vcpi_slots);
+
+ /**
+ * drm_dp_mst_update_slots() - updates the slot info depending on the DP ecoding format
+ * @mst_state: mst_state to update
+ * @link_encoding_cap: the ecoding format on the link
+ */
+ void drm_dp_mst_update_slots(struct drm_dp_mst_topology_state *mst_state, uint8_t link_encoding_cap)
+ {
+ if (link_encoding_cap == DP_CAP_ANSI_128B132B) {
+ mst_state->total_avail_slots = 64;
+ mst_state->start_slot = 0;
+ } else {
+ mst_state->total_avail_slots = 63;
+ mst_state->start_slot = 1;
+ }
+
+ DRM_DEBUG_KMS("%s encoding format on mst_state 0x%p\n",
+ (link_encoding_cap == DP_CAP_ANSI_128B132B) ? "128b/132b":"8b/10b",
+ mst_state);
+ }
+ EXPORT_SYMBOL(drm_dp_mst_update_slots);
+
+ /**
+ * drm_dp_mst_allocate_vcpi() - Allocate a virtual channel
+ * @mgr: manager for this port
+ * @port: port to allocate a virtual channel for.
+ * @pbn: payload bandwidth number to request
+ * @slots: returned number of slots for this PBN.
+ */
+ bool drm_dp_mst_allocate_vcpi(struct drm_dp_mst_topology_mgr *mgr,
+ struct drm_dp_mst_port *port, int pbn, int slots)
+ {
+ int ret;
+
+ if (slots < 0)
+ return false;
+
+ port = drm_dp_mst_topology_get_port_validated(mgr, port);
+ if (!port)
+ return false;
+
+ if (port->vcpi.vcpi > 0) {
+ drm_dbg_kms(mgr->dev,
+ "payload: vcpi %d already allocated for pbn %d - requested pbn %d\n",
+ port->vcpi.vcpi, port->vcpi.pbn, pbn);
+ if (pbn == port->vcpi.pbn) {
+ drm_dp_mst_topology_put_port(port);
+ return true;
+ }
+ }
+
+ ret = drm_dp_init_vcpi(mgr, &port->vcpi, pbn, slots);
+ if (ret) {
+ drm_dbg_kms(mgr->dev, "failed to init vcpi slots=%d ret=%d\n",
+ DIV_ROUND_UP(pbn, mgr->pbn_div), ret);
+ drm_dp_mst_topology_put_port(port);
+ goto out;
+ }
+ drm_dbg_kms(mgr->dev, "initing vcpi for pbn=%d slots=%d\n", pbn, port->vcpi.num_slots);
+
+ /* Keep port allocated until its payload has been removed */
+ drm_dp_mst_get_port_malloc(port);
+ drm_dp_mst_topology_put_port(port);
+ return true;
+ out:
+ return false;
+ }
+ EXPORT_SYMBOL(drm_dp_mst_allocate_vcpi);
+
+ int drm_dp_mst_get_vcpi_slots(struct drm_dp_mst_topology_mgr *mgr, struct drm_dp_mst_port *port)
+ {
+ int slots = 0;
+
+ port = drm_dp_mst_topology_get_port_validated(mgr, port);
+ if (!port)
+ return slots;
+
+ slots = port->vcpi.num_slots;
+ drm_dp_mst_topology_put_port(port);
+ return slots;
+ }
+ EXPORT_SYMBOL(drm_dp_mst_get_vcpi_slots);
+
+ /**
+ * drm_dp_mst_reset_vcpi_slots() - Reset number of slots to 0 for VCPI
+ * @mgr: manager for this port
+ * @port: unverified pointer to a port.
+ *
+ * This just resets the number of slots for the ports VCPI for later programming.
+ */
+ void drm_dp_mst_reset_vcpi_slots(struct drm_dp_mst_topology_mgr *mgr, struct drm_dp_mst_port *port)
+ {
+ /*
+ * A port with VCPI will remain allocated until its VCPI is
+ * released, no verified ref needed
+ */
+
+ port->vcpi.num_slots = 0;
+ }
+ EXPORT_SYMBOL(drm_dp_mst_reset_vcpi_slots);
+
+ /**
+ * drm_dp_mst_deallocate_vcpi() - deallocate a VCPI
+ * @mgr: manager for this port
+ * @port: port to deallocate vcpi for
+ *
+ * This can be called unconditionally, regardless of whether
+ * drm_dp_mst_allocate_vcpi() succeeded or not.
+ */
+ void drm_dp_mst_deallocate_vcpi(struct drm_dp_mst_topology_mgr *mgr,
+ struct drm_dp_mst_port *port)
+ {
+ bool skip;
+
+ if (!port->vcpi.vcpi)
+ return;
+
+ mutex_lock(&mgr->lock);
+ skip = !drm_dp_mst_port_downstream_of_branch(port, mgr->mst_primary);
+ mutex_unlock(&mgr->lock);
+
+ if (skip)
+ return;
+
+ drm_dp_mst_put_payload_id(mgr, port->vcpi.vcpi);
+ port->vcpi.num_slots = 0;
+ port->vcpi.pbn = 0;
+ port->vcpi.aligned_pbn = 0;
+ port->vcpi.vcpi = 0;
+ drm_dp_mst_put_port_malloc(port);
+ }
+ EXPORT_SYMBOL(drm_dp_mst_deallocate_vcpi);
+
+ static int drm_dp_dpcd_write_payload(struct drm_dp_mst_topology_mgr *mgr,
+ int id, struct drm_dp_payload *payload)
+ {
+ u8 payload_alloc[3], status;
+ int ret;
+ int retries = 0;
+
+ drm_dp_dpcd_writeb(mgr->aux, DP_PAYLOAD_TABLE_UPDATE_STATUS,
+ DP_PAYLOAD_TABLE_UPDATED);
+
+ payload_alloc[0] = id;
+ payload_alloc[1] = payload->start_slot;
+ payload_alloc[2] = payload->num_slots;
+
+ ret = drm_dp_dpcd_write(mgr->aux, DP_PAYLOAD_ALLOCATE_SET, payload_alloc, 3);
+ if (ret != 3) {
+ drm_dbg_kms(mgr->dev, "failed to write payload allocation %d\n", ret);
+ goto fail;
+ }
+
+ retry:
+ ret = drm_dp_dpcd_readb(mgr->aux, DP_PAYLOAD_TABLE_UPDATE_STATUS, &status);
+ if (ret < 0) {
+ drm_dbg_kms(mgr->dev, "failed to read payload table status %d\n", ret);
+ goto fail;
+ }
+
+ if (!(status & DP_PAYLOAD_TABLE_UPDATED)) {
+ retries++;
+ if (retries < 20) {
+ usleep_range(10000, 20000);
+ goto retry;
+ }
+ drm_dbg_kms(mgr->dev, "status not set after read payload table status %d\n",
+ status);
+ ret = -EINVAL;
+ goto fail;
+ }
+ ret = 0;
+ fail:
+ return ret;
+ }
+
+ static int do_get_act_status(struct drm_dp_aux *aux)
+ {
+ int ret;
+ u8 status;
+
+ ret = drm_dp_dpcd_readb(aux, DP_PAYLOAD_TABLE_UPDATE_STATUS, &status);
+ if (ret < 0)
+ return ret;
+
+ return status;
+ }
+
+ /**
+ * drm_dp_check_act_status() - Polls for ACT handled status.
+ * @mgr: manager to use
+ *
+ * Tries waiting for the MST hub to finish updating it's payload table by
+ * polling for the ACT handled bit for up to 3 seconds (yes-some hubs really
+ * take that long).
+ *
+ * Returns:
+ * 0 if the ACT was handled in time, negative error code on failure.
+ */
+ int drm_dp_check_act_status(struct drm_dp_mst_topology_mgr *mgr)
+ {
+ /*
+ * There doesn't seem to be any recommended retry count or timeout in
+ * the MST specification. Since some hubs have been observed to take
+ * over 1 second to update their payload allocations under certain
+ * conditions, we use a rather large timeout value.
+ */
+ const int timeout_ms = 3000;
+ int ret, status;
+
+ ret = readx_poll_timeout(do_get_act_status, mgr->aux, status,
+ status & DP_PAYLOAD_ACT_HANDLED || status < 0,
+ 200, timeout_ms * USEC_PER_MSEC);
+ if (ret < 0 && status >= 0) {
+ drm_err(mgr->dev, "Failed to get ACT after %dms, last status: %02x\n",
+ timeout_ms, status);
+ return -EINVAL;
+ } else if (status < 0) {
+ /*
+ * Failure here isn't unexpected - the hub may have
+ * just been unplugged
+ */
+ drm_dbg_kms(mgr->dev, "Failed to read payload table status: %d\n", status);
+ return status;
+ }
+
+ return 0;
+ }
+ EXPORT_SYMBOL(drm_dp_check_act_status);
+
+ /**
+ * drm_dp_calc_pbn_mode() - Calculate the PBN for a mode.
+ * @clock: dot clock for the mode
+ * @bpp: bpp for the mode.
+ * @dsc: DSC mode. If true, bpp has units of 1/16 of a bit per pixel
+ *
+ * This uses the formula in the spec to calculate the PBN value for a mode.
+ */
+ int drm_dp_calc_pbn_mode(int clock, int bpp, bool dsc)
+ {
+ /*
+ * margin 5300ppm + 300ppm ~ 0.6% as per spec, factor is 1.006
+ * The unit of 54/64Mbytes/sec is an arbitrary unit chosen based on
+ * common multiplier to render an integer PBN for all link rate/lane
+ * counts combinations
+ * calculate
+ * peak_kbps *= (1006/1000)
+ * peak_kbps *= (64/54)
+ * peak_kbps *= 8 convert to bytes
+ *
+ * If the bpp is in units of 1/16, further divide by 16. Put this
+ * factor in the numerator rather than the denominator to avoid
+ * integer overflow
+ */
+
+ if (dsc)
+ return DIV_ROUND_UP_ULL(mul_u32_u32(clock * (bpp / 16), 64 * 1006),
+ 8 * 54 * 1000 * 1000);
+
+ return DIV_ROUND_UP_ULL(mul_u32_u32(clock * bpp, 64 * 1006),
+ 8 * 54 * 1000 * 1000);
+ }
+ EXPORT_SYMBOL(drm_dp_calc_pbn_mode);
+
+ /* we want to kick the TX after we've ack the up/down IRQs. */
+ static void drm_dp_mst_kick_tx(struct drm_dp_mst_topology_mgr *mgr)
+ {
+ queue_work(system_long_wq, &mgr->tx_work);
+ }
+
+ /*
+ * Helper function for parsing DP device types into convenient strings
+ * for use with dp_mst_topology
+ */
+ static const char *pdt_to_string(u8 pdt)
+ {
+ switch (pdt) {
+ case DP_PEER_DEVICE_NONE:
+ return "NONE";
+ case DP_PEER_DEVICE_SOURCE_OR_SST:
+ return "SOURCE OR SST";
+ case DP_PEER_DEVICE_MST_BRANCHING:
+ return "MST BRANCHING";
+ case DP_PEER_DEVICE_SST_SINK:
+ return "SST SINK";
+ case DP_PEER_DEVICE_DP_LEGACY_CONV:
+ return "DP LEGACY CONV";
+ default:
+ return "ERR";
+ }
+ }
+
+ static void drm_dp_mst_dump_mstb(struct seq_file *m,
+ struct drm_dp_mst_branch *mstb)
+ {
+ struct drm_dp_mst_port *port;
+ int tabs = mstb->lct;
+ char prefix[10];
+ int i;
+
+ for (i = 0; i < tabs; i++)
+ prefix[i] = '\t';
+ prefix[i] = '\0';
+
+ seq_printf(m, "%smstb - [%p]: num_ports: %d\n", prefix, mstb, mstb->num_ports);
+ list_for_each_entry(port, &mstb->ports, next) {
+ seq_printf(m, "%sport %d - [%p] (%s - %s): ddps: %d, ldps: %d, sdp: %d/%d, fec: %s, conn: %p\n",
+ prefix,
+ port->port_num,
+ port,
+ port->input ? "input" : "output",
+ pdt_to_string(port->pdt),
+ port->ddps,
+ port->ldps,
+ port->num_sdp_streams,
+ port->num_sdp_stream_sinks,
+ port->fec_capable ? "true" : "false",
+ port->connector);
+ if (port->mstb)
+ drm_dp_mst_dump_mstb(m, port->mstb);
+ }
+ }
+
+ #define DP_PAYLOAD_TABLE_SIZE 64
+
+ static bool dump_dp_payload_table(struct drm_dp_mst_topology_mgr *mgr,
+ char *buf)
+ {
+ int i;
+
+ for (i = 0; i < DP_PAYLOAD_TABLE_SIZE; i += 16) {
+ if (drm_dp_dpcd_read(mgr->aux,
+ DP_PAYLOAD_TABLE_UPDATE_STATUS + i,
+ &buf[i], 16) != 16)
+ return false;
+ }
+ return true;
+ }
+
+ static void fetch_monitor_name(struct drm_dp_mst_topology_mgr *mgr,
+ struct drm_dp_mst_port *port, char *name,
+ int namelen)
+ {
+ struct edid *mst_edid;
+
+ mst_edid = drm_dp_mst_get_edid(port->connector, mgr, port);
+ drm_edid_get_monitor_name(mst_edid, name, namelen);
+ }
+
+ /**
+ * drm_dp_mst_dump_topology(): dump topology to seq file.
+ * @m: seq_file to dump output to
+ * @mgr: manager to dump current topology for.
+ *
+ * helper to dump MST topology to a seq file for debugfs.
+ */
+ void drm_dp_mst_dump_topology(struct seq_file *m,
+ struct drm_dp_mst_topology_mgr *mgr)
+ {
+ int i;
+ struct drm_dp_mst_port *port;
+
+ mutex_lock(&mgr->lock);
+ if (mgr->mst_primary)
+ drm_dp_mst_dump_mstb(m, mgr->mst_primary);
+
+ /* dump VCPIs */
+ mutex_unlock(&mgr->lock);
+
+ mutex_lock(&mgr->payload_lock);
+ seq_printf(m, "\n*** VCPI Info ***\n");
+ seq_printf(m, "payload_mask: %lx, vcpi_mask: %lx, max_payloads: %d\n", mgr->payload_mask, mgr->vcpi_mask, mgr->max_payloads);
+
+ seq_printf(m, "\n| idx | port # | vcp_id | # slots | sink name |\n");
+ for (i = 0; i < mgr->max_payloads; i++) {
+ if (mgr->proposed_vcpis[i]) {
+ char name[14];
+
+ port = container_of(mgr->proposed_vcpis[i], struct drm_dp_mst_port, vcpi);
+ fetch_monitor_name(mgr, port, name, sizeof(name));
+ seq_printf(m, "%10d%10d%10d%10d%20s\n",
+ i,
+ port->port_num,
+ port->vcpi.vcpi,
+ port->vcpi.num_slots,
+ (*name != 0) ? name : "Unknown");
+ } else
+ seq_printf(m, "%6d - Unused\n", i);
+ }
+ seq_printf(m, "\n*** Payload Info ***\n");
+ seq_printf(m, "| idx | state | start slot | # slots |\n");
+ for (i = 0; i < mgr->max_payloads; i++) {
+ seq_printf(m, "%10d%10d%15d%10d\n",
+ i,
+ mgr->payloads[i].payload_state,
+ mgr->payloads[i].start_slot,
+ mgr->payloads[i].num_slots);
+ }
+ mutex_unlock(&mgr->payload_lock);
+
+ seq_printf(m, "\n*** DPCD Info ***\n");
+ mutex_lock(&mgr->lock);
+ if (mgr->mst_primary) {
+ u8 buf[DP_PAYLOAD_TABLE_SIZE];
+ int ret;
+
+ ret = drm_dp_dpcd_read(mgr->aux, DP_DPCD_REV, buf, DP_RECEIVER_CAP_SIZE);
+ if (ret) {
+ seq_printf(m, "dpcd read failed\n");
+ goto out;
+ }
+ seq_printf(m, "dpcd: %*ph\n", DP_RECEIVER_CAP_SIZE, buf);
+
+ ret = drm_dp_dpcd_read(mgr->aux, DP_FAUX_CAP, buf, 2);
+ if (ret) {
+ seq_printf(m, "faux/mst read failed\n");
+ goto out;
+ }
+ seq_printf(m, "faux/mst: %*ph\n", 2, buf);
+
+ ret = drm_dp_dpcd_read(mgr->aux, DP_MSTM_CTRL, buf, 1);
+ if (ret) {
+ seq_printf(m, "mst ctrl read failed\n");
+ goto out;
+ }
+ seq_printf(m, "mst ctrl: %*ph\n", 1, buf);
+
+ /* dump the standard OUI branch header */
+ ret = drm_dp_dpcd_read(mgr->aux, DP_BRANCH_OUI, buf, DP_BRANCH_OUI_HEADER_SIZE);
+ if (ret) {
+ seq_printf(m, "branch oui read failed\n");
+ goto out;
+ }
+ seq_printf(m, "branch oui: %*phN devid: ", 3, buf);
+
+ for (i = 0x3; i < 0x8 && buf[i]; i++)
+ seq_printf(m, "%c", buf[i]);
+ seq_printf(m, " revision: hw: %x.%x sw: %x.%x\n",
+ buf[0x9] >> 4, buf[0x9] & 0xf, buf[0xa], buf[0xb]);
+ if (dump_dp_payload_table(mgr, buf))
+ seq_printf(m, "payload table: %*ph\n", DP_PAYLOAD_TABLE_SIZE, buf);
+ }
+
+ out:
+ mutex_unlock(&mgr->lock);
+
+ }
+ EXPORT_SYMBOL(drm_dp_mst_dump_topology);
+
+ static void drm_dp_tx_work(struct work_struct *work)
+ {
+ struct drm_dp_mst_topology_mgr *mgr = container_of(work, struct drm_dp_mst_topology_mgr, tx_work);
+
+ mutex_lock(&mgr->qlock);
+ if (!list_empty(&mgr->tx_msg_downq))
+ process_single_down_tx_qlock(mgr);
+ mutex_unlock(&mgr->qlock);
+ }
+
+ static inline void
+ drm_dp_delayed_destroy_port(struct drm_dp_mst_port *port)
+ {
+ drm_dp_port_set_pdt(port, DP_PEER_DEVICE_NONE, port->mcs);
+
+ if (port->connector) {
+ drm_connector_unregister(port->connector);
+ drm_connector_put(port->connector);
+ }
+
+ drm_dp_mst_put_port_malloc(port);
+ }
+
+ static inline void
+ drm_dp_delayed_destroy_mstb(struct drm_dp_mst_branch *mstb)
+ {
+ struct drm_dp_mst_topology_mgr *mgr = mstb->mgr;
+ struct drm_dp_mst_port *port, *port_tmp;
+ struct drm_dp_sideband_msg_tx *txmsg, *txmsg_tmp;
+ bool wake_tx = false;
+
+ mutex_lock(&mgr->lock);
+ list_for_each_entry_safe(port, port_tmp, &mstb->ports, next) {
+ list_del(&port->next);
+ drm_dp_mst_topology_put_port(port);
+ }
+ mutex_unlock(&mgr->lock);
+
+ /* drop any tx slot msg */
+ mutex_lock(&mstb->mgr->qlock);
+ list_for_each_entry_safe(txmsg, txmsg_tmp, &mgr->tx_msg_downq, next) {
+ if (txmsg->dst != mstb)
+ continue;
+
+ txmsg->state = DRM_DP_SIDEBAND_TX_TIMEOUT;
+ list_del(&txmsg->next);
+ wake_tx = true;
+ }
+ mutex_unlock(&mstb->mgr->qlock);
+
+ if (wake_tx)
+ wake_up_all(&mstb->mgr->tx_waitq);
+
+ drm_dp_mst_put_mstb_malloc(mstb);
+ }
+
+ static void drm_dp_delayed_destroy_work(struct work_struct *work)
+ {
+ struct drm_dp_mst_topology_mgr *mgr =
+ container_of(work, struct drm_dp_mst_topology_mgr,
+ delayed_destroy_work);
+ bool send_hotplug = false, go_again;
+
+ /*
+ * Not a regular list traverse as we have to drop the destroy
+ * connector lock before destroying the mstb/port, to avoid AB->BA
+ * ordering between this lock and the config mutex.
+ */
+ do {
+ go_again = false;
+
+ for (;;) {
+ struct drm_dp_mst_branch *mstb;
+
+ mutex_lock(&mgr->delayed_destroy_lock);
+ mstb = list_first_entry_or_null(&mgr->destroy_branch_device_list,
+ struct drm_dp_mst_branch,
+ destroy_next);
+ if (mstb)
+ list_del(&mstb->destroy_next);
+ mutex_unlock(&mgr->delayed_destroy_lock);
+
+ if (!mstb)
+ break;
+
+ drm_dp_delayed_destroy_mstb(mstb);
+ go_again = true;
+ }
+
+ for (;;) {
+ struct drm_dp_mst_port *port;
+
+ mutex_lock(&mgr->delayed_destroy_lock);
+ port = list_first_entry_or_null(&mgr->destroy_port_list,
+ struct drm_dp_mst_port,
+ next);
+ if (port)
+ list_del(&port->next);
+ mutex_unlock(&mgr->delayed_destroy_lock);
+
+ if (!port)
+ break;
+
+ drm_dp_delayed_destroy_port(port);
+ send_hotplug = true;
+ go_again = true;
+ }
+ } while (go_again);
+
+ if (send_hotplug)
+ drm_kms_helper_hotplug_event(mgr->dev);
+ }
+
+ static struct drm_private_state *
+ drm_dp_mst_duplicate_state(struct drm_private_obj *obj)
+ {
+ struct drm_dp_mst_topology_state *state, *old_state =
+ to_dp_mst_topology_state(obj->state);
+ struct drm_dp_vcpi_allocation *pos, *vcpi;
+
+ state = kmemdup(old_state, sizeof(*state), GFP_KERNEL);
+ if (!state)
+ return NULL;
+
+ __drm_atomic_helper_private_obj_duplicate_state(obj, &state->base);
+
+ INIT_LIST_HEAD(&state->vcpis);
+
+ list_for_each_entry(pos, &old_state->vcpis, next) {
+ /* Prune leftover freed VCPI allocations */
+ if (!pos->vcpi)
+ continue;
+
+ vcpi = kmemdup(pos, sizeof(*vcpi), GFP_KERNEL);
+ if (!vcpi)
+ goto fail;
+
+ drm_dp_mst_get_port_malloc(vcpi->port);
+ list_add(&vcpi->next, &state->vcpis);
+ }
+
+ return &state->base;
+
+ fail:
+ list_for_each_entry_safe(pos, vcpi, &state->vcpis, next) {
+ drm_dp_mst_put_port_malloc(pos->port);
+ kfree(pos);
+ }
+ kfree(state);
+
+ return NULL;
+ }
+
+ static void drm_dp_mst_destroy_state(struct drm_private_obj *obj,
+ struct drm_private_state *state)
+ {
+ struct drm_dp_mst_topology_state *mst_state =
+ to_dp_mst_topology_state(state);
+ struct drm_dp_vcpi_allocation *pos, *tmp;
+
+ list_for_each_entry_safe(pos, tmp, &mst_state->vcpis, next) {
+ /* We only keep references to ports with non-zero VCPIs */
+ if (pos->vcpi)
+ drm_dp_mst_put_port_malloc(pos->port);
+ kfree(pos);
+ }
+
+ kfree(mst_state);
+ }
+
+ static bool drm_dp_mst_port_downstream_of_branch(struct drm_dp_mst_port *port,
+ struct drm_dp_mst_branch *branch)
+ {
+ while (port->parent) {
+ if (port->parent == branch)
+ return true;
+
+ if (port->parent->port_parent)
+ port = port->parent->port_parent;
+ else
+ break;
+ }
+ return false;
+ }
+
+ static int
+ drm_dp_mst_atomic_check_port_bw_limit(struct drm_dp_mst_port *port,
+ struct drm_dp_mst_topology_state *state);
+
+ static int
+ drm_dp_mst_atomic_check_mstb_bw_limit(struct drm_dp_mst_branch *mstb,
+ struct drm_dp_mst_topology_state *state)
+ {
+ struct drm_dp_vcpi_allocation *vcpi;
+ struct drm_dp_mst_port *port;
+ int pbn_used = 0, ret;
+ bool found = false;
+
+ /* Check that we have at least one port in our state that's downstream
+ * of this branch, otherwise we can skip this branch
+ */
+ list_for_each_entry(vcpi, &state->vcpis, next) {
+ if (!vcpi->pbn ||
+ !drm_dp_mst_port_downstream_of_branch(vcpi->port, mstb))
+ continue;
+
+ found = true;
+ break;
+ }
+ if (!found)
+ return 0;
+
+ if (mstb->port_parent)
+ drm_dbg_atomic(mstb->mgr->dev,
+ "[MSTB:%p] [MST PORT:%p] Checking bandwidth limits on [MSTB:%p]\n",
+ mstb->port_parent->parent, mstb->port_parent, mstb);
+ else
+ drm_dbg_atomic(mstb->mgr->dev, "[MSTB:%p] Checking bandwidth limits\n", mstb);
+
+ list_for_each_entry(port, &mstb->ports, next) {
+ ret = drm_dp_mst_atomic_check_port_bw_limit(port, state);
+ if (ret < 0)
+ return ret;
+
+ pbn_used += ret;
+ }
+
+ return pbn_used;
+ }
+
+ static int
+ drm_dp_mst_atomic_check_port_bw_limit(struct drm_dp_mst_port *port,
+ struct drm_dp_mst_topology_state *state)
+ {
+ struct drm_dp_vcpi_allocation *vcpi;
+ int pbn_used = 0;
+
+ if (port->pdt == DP_PEER_DEVICE_NONE)
+ return 0;
+
+ if (drm_dp_mst_is_end_device(port->pdt, port->mcs)) {
+ bool found = false;
+
+ list_for_each_entry(vcpi, &state->vcpis, next) {
+ if (vcpi->port != port)
+ continue;
+ if (!vcpi->pbn)
+ return 0;
+
+ found = true;
+ break;
+ }
+ if (!found)
+ return 0;
+
+ /*
+ * This could happen if the sink deasserted its HPD line, but
+ * the branch device still reports it as attached (PDT != NONE).
+ */
+ if (!port->full_pbn) {
+ drm_dbg_atomic(port->mgr->dev,
+ "[MSTB:%p] [MST PORT:%p] no BW available for the port\n",
+ port->parent, port);
+ return -EINVAL;
+ }
+
+ pbn_used = vcpi->pbn;
+ } else {
+ pbn_used = drm_dp_mst_atomic_check_mstb_bw_limit(port->mstb,
+ state);
+ if (pbn_used <= 0)
+ return pbn_used;
+ }
+
+ if (pbn_used > port->full_pbn) {
+ drm_dbg_atomic(port->mgr->dev,
+ "[MSTB:%p] [MST PORT:%p] required PBN of %d exceeds port limit of %d\n",
+ port->parent, port, pbn_used, port->full_pbn);
+ return -ENOSPC;
+ }
+
+ drm_dbg_atomic(port->mgr->dev, "[MSTB:%p] [MST PORT:%p] uses %d out of %d PBN\n",
+ port->parent, port, pbn_used, port->full_pbn);
+
+ return pbn_used;
+ }
+
+ static inline int
+ drm_dp_mst_atomic_check_vcpi_alloc_limit(struct drm_dp_mst_topology_mgr *mgr,
+ struct drm_dp_mst_topology_state *mst_state)
+ {
+ struct drm_dp_vcpi_allocation *vcpi;
+ int avail_slots = mst_state->total_avail_slots, payload_count = 0;
+
+ list_for_each_entry(vcpi, &mst_state->vcpis, next) {
+ /* Releasing VCPI is always OK-even if the port is gone */
+ if (!vcpi->vcpi) {
+ drm_dbg_atomic(mgr->dev, "[MST PORT:%p] releases all VCPI slots\n",
+ vcpi->port);
+ continue;
+ }
+
+ drm_dbg_atomic(mgr->dev, "[MST PORT:%p] requires %d vcpi slots\n",
+ vcpi->port, vcpi->vcpi);
+
+ avail_slots -= vcpi->vcpi;
+ if (avail_slots < 0) {
+ drm_dbg_atomic(mgr->dev,
+ "[MST PORT:%p] not enough VCPI slots in mst state %p (avail=%d)\n",
+ vcpi->port, mst_state, avail_slots + vcpi->vcpi);
+ return -ENOSPC;
+ }
+
+ if (++payload_count > mgr->max_payloads) {
+ drm_dbg_atomic(mgr->dev,
+ "[MST MGR:%p] state %p has too many payloads (max=%d)\n",
+ mgr, mst_state, mgr->max_payloads);
+ return -EINVAL;
+ }
+ }
+ drm_dbg_atomic(mgr->dev, "[MST MGR:%p] mst state %p VCPI avail=%d used=%d\n",
+ mgr, mst_state, avail_slots, mst_state->total_avail_slots - avail_slots);
+
+ return 0;
+ }
+
+ /**
+ * drm_dp_mst_add_affected_dsc_crtcs
+ * @state: Pointer to the new struct drm_dp_mst_topology_state
+ * @mgr: MST topology manager
+ *
+ * Whenever there is a change in mst topology
+ * DSC configuration would have to be recalculated
+ * therefore we need to trigger modeset on all affected
+ * CRTCs in that topology
+ *
+ * See also:
+ * drm_dp_mst_atomic_enable_dsc()
+ */
+ int drm_dp_mst_add_affected_dsc_crtcs(struct drm_atomic_state *state, struct drm_dp_mst_topology_mgr *mgr)
+ {
+ struct drm_dp_mst_topology_state *mst_state;
+ struct drm_dp_vcpi_allocation *pos;
+ struct drm_connector *connector;
+ struct drm_connector_state *conn_state;
+ struct drm_crtc *crtc;
+ struct drm_crtc_state *crtc_state;
+
+ mst_state = drm_atomic_get_mst_topology_state(state, mgr);
+
+ if (IS_ERR(mst_state))
+ return -EINVAL;
+
+ list_for_each_entry(pos, &mst_state->vcpis, next) {
+
+ connector = pos->port->connector;
+
+ if (!connector)
+ return -EINVAL;
+
+ conn_state = drm_atomic_get_connector_state(state, connector);
+
+ if (IS_ERR(conn_state))
+ return PTR_ERR(conn_state);
+
+ crtc = conn_state->crtc;
+
+ if (!crtc)
+ continue;
+
+ if (!drm_dp_mst_dsc_aux_for_port(pos->port))
+ continue;
+
+ crtc_state = drm_atomic_get_crtc_state(mst_state->base.state, crtc);
+
+ if (IS_ERR(crtc_state))
+ return PTR_ERR(crtc_state);
+
+ drm_dbg_atomic(mgr->dev, "[MST MGR:%p] Setting mode_changed flag on CRTC %p\n",
+ mgr, crtc);
+
+ crtc_state->mode_changed = true;
+ }
+ return 0;
+ }
+ EXPORT_SYMBOL(drm_dp_mst_add_affected_dsc_crtcs);
+
+ /**
+ * drm_dp_mst_atomic_enable_dsc - Set DSC Enable Flag to On/Off
+ * @state: Pointer to the new drm_atomic_state
+ * @port: Pointer to the affected MST Port
+ * @pbn: Newly recalculated bw required for link with DSC enabled
+ * @pbn_div: Divider to calculate correct number of pbn per slot
+ * @enable: Boolean flag to enable or disable DSC on the port
+ *
+ * This function enables DSC on the given Port
+ * by recalculating its vcpi from pbn provided
+ * and sets dsc_enable flag to keep track of which
+ * ports have DSC enabled
+ *
+ */
+ int drm_dp_mst_atomic_enable_dsc(struct drm_atomic_state *state,
+ struct drm_dp_mst_port *port,
+ int pbn, int pbn_div,
+ bool enable)
+ {
+ struct drm_dp_mst_topology_state *mst_state;
+ struct drm_dp_vcpi_allocation *pos;
+ bool found = false;
+ int vcpi = 0;
+
+ mst_state = drm_atomic_get_mst_topology_state(state, port->mgr);
+
+ if (IS_ERR(mst_state))
+ return PTR_ERR(mst_state);
+
+ list_for_each_entry(pos, &mst_state->vcpis, next) {
+ if (pos->port == port) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ drm_dbg_atomic(state->dev,
+ "[MST PORT:%p] Couldn't find VCPI allocation in mst state %p\n",
+ port, mst_state);
+ return -EINVAL;
+ }
+
+ if (pos->dsc_enabled == enable) {
+ drm_dbg_atomic(state->dev,
+ "[MST PORT:%p] DSC flag is already set to %d, returning %d VCPI slots\n",
+ port, enable, pos->vcpi);
+ vcpi = pos->vcpi;
+ }
+
+ if (enable) {
+ vcpi = drm_dp_atomic_find_vcpi_slots(state, port->mgr, port, pbn, pbn_div);
+ drm_dbg_atomic(state->dev,
+ "[MST PORT:%p] Enabling DSC flag, reallocating %d VCPI slots on the port\n",
+ port, vcpi);
+ if (vcpi < 0)
+ return -EINVAL;
+ }
+
+ pos->dsc_enabled = enable;
+
+ return vcpi;
+ }
+ EXPORT_SYMBOL(drm_dp_mst_atomic_enable_dsc);
+ /**
+ * drm_dp_mst_atomic_check - Check that the new state of an MST topology in an
+ * atomic update is valid
+ * @state: Pointer to the new &struct drm_dp_mst_topology_state
+ *
+ * Checks the given topology state for an atomic update to ensure that it's
+ * valid. This includes checking whether there's enough bandwidth to support
+ * the new VCPI allocations in the atomic update.
+ *
+ * Any atomic drivers supporting DP MST must make sure to call this after
+ * checking the rest of their state in their
+ * &drm_mode_config_funcs.atomic_check() callback.
+ *
+ * See also:
+ * drm_dp_atomic_find_vcpi_slots()
+ * drm_dp_atomic_release_vcpi_slots()
+ *
+ * Returns:
+ *
+ * 0 if the new state is valid, negative error code otherwise.
+ */
+ int drm_dp_mst_atomic_check(struct drm_atomic_state *state)
+ {
+ struct drm_dp_mst_topology_mgr *mgr;
+ struct drm_dp_mst_topology_state *mst_state;
+ int i, ret = 0;
+
+ for_each_new_mst_mgr_in_state(state, mgr, mst_state, i) {
+ if (!mgr->mst_state)
+ continue;
+
+ ret = drm_dp_mst_atomic_check_vcpi_alloc_limit(mgr, mst_state);
+ if (ret)
+ break;
+
+ mutex_lock(&mgr->lock);
+ ret = drm_dp_mst_atomic_check_mstb_bw_limit(mgr->mst_primary,
+ mst_state);
+ mutex_unlock(&mgr->lock);
+ if (ret < 0)
+ break;
+ else
+ ret = 0;
+ }
+
+ return ret;
+ }
+ EXPORT_SYMBOL(drm_dp_mst_atomic_check);
+
+ const struct drm_private_state_funcs drm_dp_mst_topology_state_funcs = {
+ .atomic_duplicate_state = drm_dp_mst_duplicate_state,
+ .atomic_destroy_state = drm_dp_mst_destroy_state,
+ };
+ EXPORT_SYMBOL(drm_dp_mst_topology_state_funcs);
+
+ /**
+ * drm_atomic_get_mst_topology_state: get MST topology state
+ *
+ * @state: global atomic state
+ * @mgr: MST topology manager, also the private object in this case
+ *
+ * This function wraps drm_atomic_get_priv_obj_state() passing in the MST atomic
+ * state vtable so that the private object state returned is that of a MST
+ * topology object. Also, drm_atomic_get_private_obj_state() expects the caller
+ * to care of the locking, so warn if don't hold the connection_mutex.
+ *
+ * RETURNS:
+ *
+ * The MST topology state or error pointer.
+ */
+ struct drm_dp_mst_topology_state *drm_atomic_get_mst_topology_state(struct drm_atomic_state *state,
+ struct drm_dp_mst_topology_mgr *mgr)
+ {
+ return to_dp_mst_topology_state(drm_atomic_get_private_obj_state(state, &mgr->base));
+ }
+ EXPORT_SYMBOL(drm_atomic_get_mst_topology_state);
+
+ /**
+ * drm_dp_mst_topology_mgr_init - initialise a topology manager
+ * @mgr: manager struct to initialise
+ * @dev: device providing this structure - for i2c addition.
+ * @aux: DP helper aux channel to talk to this device
+ * @max_dpcd_transaction_bytes: hw specific DPCD transaction limit
+ * @max_payloads: maximum number of payloads this GPU can source
+ * @max_lane_count: maximum number of lanes this GPU supports
+ * @max_link_rate: maximum link rate per lane this GPU supports in kHz
+ * @conn_base_id: the connector object ID the MST device is connected to.
+ *
+ * Return 0 for success, or negative error code on failure
+ */
+ int drm_dp_mst_topology_mgr_init(struct drm_dp_mst_topology_mgr *mgr,
+ struct drm_device *dev, struct drm_dp_aux *aux,
+ int max_dpcd_transaction_bytes, int max_payloads,
+ int max_lane_count, int max_link_rate,
+ int conn_base_id)
+ {
+ struct drm_dp_mst_topology_state *mst_state;
+
+ mutex_init(&mgr->lock);
+ mutex_init(&mgr->qlock);
+ mutex_init(&mgr->payload_lock);
+ mutex_init(&mgr->delayed_destroy_lock);
+ mutex_init(&mgr->up_req_lock);
+ mutex_init(&mgr->probe_lock);
+ #if IS_ENABLED(CONFIG_DRM_DEBUG_DP_MST_TOPOLOGY_REFS)
+ mutex_init(&mgr->topology_ref_history_lock);
++ stack_depot_init();
+ #endif
+ INIT_LIST_HEAD(&mgr->tx_msg_downq);
+ INIT_LIST_HEAD(&mgr->destroy_port_list);
+ INIT_LIST_HEAD(&mgr->destroy_branch_device_list);
+ INIT_LIST_HEAD(&mgr->up_req_list);
+
+ /*
+ * delayed_destroy_work will be queued on a dedicated WQ, so that any
+ * requeuing will be also flushed when deiniting the topology manager.
+ */
+ mgr->delayed_destroy_wq = alloc_ordered_workqueue("drm_dp_mst_wq", 0);
+ if (mgr->delayed_destroy_wq == NULL)
+ return -ENOMEM;
+
+ INIT_WORK(&mgr->work, drm_dp_mst_link_probe_work);
+ INIT_WORK(&mgr->tx_work, drm_dp_tx_work);
+ INIT_WORK(&mgr->delayed_destroy_work, drm_dp_delayed_destroy_work);
+ INIT_WORK(&mgr->up_req_work, drm_dp_mst_up_req_work);
+ init_waitqueue_head(&mgr->tx_waitq);
+ mgr->dev = dev;
+ mgr->aux = aux;
+ mgr->max_dpcd_transaction_bytes = max_dpcd_transaction_bytes;
+ mgr->max_payloads = max_payloads;
+ mgr->max_lane_count = max_lane_count;
+ mgr->max_link_rate = max_link_rate;
+ mgr->conn_base_id = conn_base_id;
+ if (max_payloads + 1 > sizeof(mgr->payload_mask) * 8 ||
+ max_payloads + 1 > sizeof(mgr->vcpi_mask) * 8)
+ return -EINVAL;
+ mgr->payloads = kcalloc(max_payloads, sizeof(struct drm_dp_payload), GFP_KERNEL);
+ if (!mgr->payloads)
+ return -ENOMEM;
+ mgr->proposed_vcpis = kcalloc(max_payloads, sizeof(struct drm_dp_vcpi *), GFP_KERNEL);
+ if (!mgr->proposed_vcpis)
+ return -ENOMEM;
+ set_bit(0, &mgr->payload_mask);
+
+ mst_state = kzalloc(sizeof(*mst_state), GFP_KERNEL);
+ if (mst_state == NULL)
+ return -ENOMEM;
+
+ mst_state->total_avail_slots = 63;
+ mst_state->start_slot = 1;
+
+ mst_state->mgr = mgr;
+ INIT_LIST_HEAD(&mst_state->vcpis);
+
+ drm_atomic_private_obj_init(dev, &mgr->base,
+ &mst_state->base,
+ &drm_dp_mst_topology_state_funcs);
+
+ return 0;
+ }
+ EXPORT_SYMBOL(drm_dp_mst_topology_mgr_init);
+
+ /**
+ * drm_dp_mst_topology_mgr_destroy() - destroy topology manager.
+ * @mgr: manager to destroy
+ */
+ void drm_dp_mst_topology_mgr_destroy(struct drm_dp_mst_topology_mgr *mgr)
+ {
+ drm_dp_mst_topology_mgr_set_mst(mgr, false);
+ flush_work(&mgr->work);
+ /* The following will also drain any requeued work on the WQ. */
+ if (mgr->delayed_destroy_wq) {
+ destroy_workqueue(mgr->delayed_destroy_wq);
+ mgr->delayed_destroy_wq = NULL;
+ }
+ mutex_lock(&mgr->payload_lock);
+ kfree(mgr->payloads);
+ mgr->payloads = NULL;
+ kfree(mgr->proposed_vcpis);
+ mgr->proposed_vcpis = NULL;
+ mutex_unlock(&mgr->payload_lock);
+ mgr->dev = NULL;
+ mgr->aux = NULL;
+ drm_atomic_private_obj_fini(&mgr->base);
+ mgr->funcs = NULL;
+
+ mutex_destroy(&mgr->delayed_destroy_lock);
+ mutex_destroy(&mgr->payload_lock);
+ mutex_destroy(&mgr->qlock);
+ mutex_destroy(&mgr->lock);
+ mutex_destroy(&mgr->up_req_lock);
+ mutex_destroy(&mgr->probe_lock);
+ #if IS_ENABLED(CONFIG_DRM_DEBUG_DP_MST_TOPOLOGY_REFS)
+ mutex_destroy(&mgr->topology_ref_history_lock);
+ #endif
+ }
+ EXPORT_SYMBOL(drm_dp_mst_topology_mgr_destroy);
+
+ static bool remote_i2c_read_ok(const struct i2c_msg msgs[], int num)
+ {
+ int i;
+
+ if (num - 1 > DP_REMOTE_I2C_READ_MAX_TRANSACTIONS)
+ return false;
+
+ for (i = 0; i < num - 1; i++) {
+ if (msgs[i].flags & I2C_M_RD ||
+ msgs[i].len > 0xff)
+ return false;
+ }
+
+ return msgs[num - 1].flags & I2C_M_RD &&
+ msgs[num - 1].len <= 0xff;
+ }
+
+ static bool remote_i2c_write_ok(const struct i2c_msg msgs[], int num)
+ {
+ int i;
+
+ for (i = 0; i < num - 1; i++) {
+ if (msgs[i].flags & I2C_M_RD || !(msgs[i].flags & I2C_M_STOP) ||
+ msgs[i].len > 0xff)
+ return false;
+ }
+
+ return !(msgs[num - 1].flags & I2C_M_RD) && msgs[num - 1].len <= 0xff;
+ }
+
+ static int drm_dp_mst_i2c_read(struct drm_dp_mst_branch *mstb,
+ struct drm_dp_mst_port *port,
+ struct i2c_msg *msgs, int num)
+ {
+ struct drm_dp_mst_topology_mgr *mgr = port->mgr;
+ unsigned int i;
+ struct drm_dp_sideband_msg_req_body msg;
+ struct drm_dp_sideband_msg_tx *txmsg = NULL;
+ int ret;
+
+ memset(&msg, 0, sizeof(msg));
+ msg.req_type = DP_REMOTE_I2C_READ;
+ msg.u.i2c_read.num_transactions = num - 1;
+ msg.u.i2c_read.port_number = port->port_num;
+ for (i = 0; i < num - 1; i++) {
+ msg.u.i2c_read.transactions[i].i2c_dev_id = msgs[i].addr;
+ msg.u.i2c_read.transactions[i].num_bytes = msgs[i].len;
+ msg.u.i2c_read.transactions[i].bytes = msgs[i].buf;
+ msg.u.i2c_read.transactions[i].no_stop_bit = !(msgs[i].flags & I2C_M_STOP);
+ }
+ msg.u.i2c_read.read_i2c_device_id = msgs[num - 1].addr;
+ msg.u.i2c_read.num_bytes_read = msgs[num - 1].len;
+
+ txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL);
+ if (!txmsg) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ txmsg->dst = mstb;
+ drm_dp_encode_sideband_req(&msg, txmsg);
+
+ drm_dp_queue_down_tx(mgr, txmsg);
+
+ ret = drm_dp_mst_wait_tx_reply(mstb, txmsg);
+ if (ret > 0) {
+
+ if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) {
+ ret = -EREMOTEIO;
+ goto out;
+ }
+ if (txmsg->reply.u.remote_i2c_read_ack.num_bytes != msgs[num - 1].len) {
+ ret = -EIO;
+ goto out;
+ }
+ memcpy(msgs[num - 1].buf, txmsg->reply.u.remote_i2c_read_ack.bytes, msgs[num - 1].len);
+ ret = num;
+ }
+ out:
+ kfree(txmsg);
+ return ret;
+ }
+
+ static int drm_dp_mst_i2c_write(struct drm_dp_mst_branch *mstb,
+ struct drm_dp_mst_port *port,
+ struct i2c_msg *msgs, int num)
+ {
+ struct drm_dp_mst_topology_mgr *mgr = port->mgr;
+ unsigned int i;
+ struct drm_dp_sideband_msg_req_body msg;
+ struct drm_dp_sideband_msg_tx *txmsg = NULL;
+ int ret;
+
+ txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL);
+ if (!txmsg) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ for (i = 0; i < num; i++) {
+ memset(&msg, 0, sizeof(msg));
+ msg.req_type = DP_REMOTE_I2C_WRITE;
+ msg.u.i2c_write.port_number = port->port_num;
+ msg.u.i2c_write.write_i2c_device_id = msgs[i].addr;
+ msg.u.i2c_write.num_bytes = msgs[i].len;
+ msg.u.i2c_write.bytes = msgs[i].buf;
+
+ memset(txmsg, 0, sizeof(*txmsg));
+ txmsg->dst = mstb;
+
+ drm_dp_encode_sideband_req(&msg, txmsg);
+ drm_dp_queue_down_tx(mgr, txmsg);
+
+ ret = drm_dp_mst_wait_tx_reply(mstb, txmsg);
+ if (ret > 0) {
+ if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) {
+ ret = -EREMOTEIO;
+ goto out;
+ }
+ } else {
+ goto out;
+ }
+ }
+ ret = num;
+ out:
+ kfree(txmsg);
+ return ret;
+ }
+
+ /* I2C device */
+ static int drm_dp_mst_i2c_xfer(struct i2c_adapter *adapter,
+ struct i2c_msg *msgs, int num)
+ {
+ struct drm_dp_aux *aux = adapter->algo_data;
+ struct drm_dp_mst_port *port =
+ container_of(aux, struct drm_dp_mst_port, aux);
+ struct drm_dp_mst_branch *mstb;
+ struct drm_dp_mst_topology_mgr *mgr = port->mgr;
+ int ret;
+
+ mstb = drm_dp_mst_topology_get_mstb_validated(mgr, port->parent);
+ if (!mstb)
+ return -EREMOTEIO;
+
+ if (remote_i2c_read_ok(msgs, num)) {
+ ret = drm_dp_mst_i2c_read(mstb, port, msgs, num);
+ } else if (remote_i2c_write_ok(msgs, num)) {
+ ret = drm_dp_mst_i2c_write(mstb, port, msgs, num);
+ } else {
+ drm_dbg_kms(mgr->dev, "Unsupported I2C transaction for MST device\n");
+ ret = -EIO;
+ }
+
+ drm_dp_mst_topology_put_mstb(mstb);
+ return ret;
+ }
+
+ static u32 drm_dp_mst_i2c_functionality(struct i2c_adapter *adapter)
+ {
+ return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL |
+ I2C_FUNC_SMBUS_READ_BLOCK_DATA |
+ I2C_FUNC_SMBUS_BLOCK_PROC_CALL |
+ I2C_FUNC_10BIT_ADDR;
+ }
+
+ static const struct i2c_algorithm drm_dp_mst_i2c_algo = {
+ .functionality = drm_dp_mst_i2c_functionality,
+ .master_xfer = drm_dp_mst_i2c_xfer,
+ };
+
+ /**
+ * drm_dp_mst_register_i2c_bus() - register an I2C adapter for I2C-over-AUX
+ * @port: The port to add the I2C bus on
+ *
+ * Returns 0 on success or a negative error code on failure.
+ */
+ static int drm_dp_mst_register_i2c_bus(struct drm_dp_mst_port *port)
+ {
+ struct drm_dp_aux *aux = &port->aux;
+ struct device *parent_dev = port->mgr->dev->dev;
+
+ aux->ddc.algo = &drm_dp_mst_i2c_algo;
+ aux->ddc.algo_data = aux;
+ aux->ddc.retries = 3;
+
+ aux->ddc.class = I2C_CLASS_DDC;
+ aux->ddc.owner = THIS_MODULE;
+ /* FIXME: set the kdev of the port's connector as parent */
+ aux->ddc.dev.parent = parent_dev;
+ aux->ddc.dev.of_node = parent_dev->of_node;
+
+ strlcpy(aux->ddc.name, aux->name ? aux->name : dev_name(parent_dev),
+ sizeof(aux->ddc.name));
+
+ return i2c_add_adapter(&aux->ddc);
+ }
+
+ /**
+ * drm_dp_mst_unregister_i2c_bus() - unregister an I2C-over-AUX adapter
+ * @port: The port to remove the I2C bus from
+ */
+ static void drm_dp_mst_unregister_i2c_bus(struct drm_dp_mst_port *port)
+ {
+ i2c_del_adapter(&port->aux.ddc);
+ }
+
+ /**
+ * drm_dp_mst_is_virtual_dpcd() - Is the given port a virtual DP Peer Device
+ * @port: The port to check
+ *
+ * A single physical MST hub object can be represented in the topology
+ * by multiple branches, with virtual ports between those branches.
+ *
+ * As of DP1.4, An MST hub with internal (virtual) ports must expose
+ * certain DPCD registers over those ports. See sections 2.6.1.1.1
+ * and 2.6.1.1.2 of Display Port specification v1.4 for details.
+ *
+ * May acquire mgr->lock
+ *
+ * Returns:
+ * true if the port is a virtual DP peer device, false otherwise
+ */
+ static bool drm_dp_mst_is_virtual_dpcd(struct drm_dp_mst_port *port)
+ {
+ struct drm_dp_mst_port *downstream_port;
+
+ if (!port || port->dpcd_rev < DP_DPCD_REV_14)
+ return false;
+
+ /* Virtual DP Sink (Internal Display Panel) */
+ if (port->port_num >= 8)
+ return true;
+
+ /* DP-to-HDMI Protocol Converter */
+ if (port->pdt == DP_PEER_DEVICE_DP_LEGACY_CONV &&
+ !port->mcs &&
+ port->ldps)
+ return true;
+
+ /* DP-to-DP */
+ mutex_lock(&port->mgr->lock);
+ if (port->pdt == DP_PEER_DEVICE_MST_BRANCHING &&
+ port->mstb &&
+ port->mstb->num_ports == 2) {
+ list_for_each_entry(downstream_port, &port->mstb->ports, next) {
+ if (downstream_port->pdt == DP_PEER_DEVICE_SST_SINK &&
+ !downstream_port->input) {
+ mutex_unlock(&port->mgr->lock);
+ return true;
+ }
+ }
+ }
+ mutex_unlock(&port->mgr->lock);
+
+ return false;
+ }
+
+ /**
+ * drm_dp_mst_dsc_aux_for_port() - Find the correct aux for DSC
+ * @port: The port to check. A leaf of the MST tree with an attached display.
+ *
+ * Depending on the situation, DSC may be enabled via the endpoint aux,
+ * the immediately upstream aux, or the connector's physical aux.
+ *
+ * This is both the correct aux to read DSC_CAPABILITY and the
+ * correct aux to write DSC_ENABLED.
+ *
+ * This operation can be expensive (up to four aux reads), so
+ * the caller should cache the return.
+ *
+ * Returns:
+ * NULL if DSC cannot be enabled on this port, otherwise the aux device
+ */
+ struct drm_dp_aux *drm_dp_mst_dsc_aux_for_port(struct drm_dp_mst_port *port)
+ {
+ struct drm_dp_mst_port *immediate_upstream_port;
+ struct drm_dp_mst_port *fec_port;
+ struct drm_dp_desc desc = {};
+ u8 endpoint_fec;
+ u8 endpoint_dsc;
+
+ if (!port)
+ return NULL;
+
+ if (port->parent->port_parent)
+ immediate_upstream_port = port->parent->port_parent;
+ else
+ immediate_upstream_port = NULL;
+
+ fec_port = immediate_upstream_port;
+ while (fec_port) {
+ /*
+ * Each physical link (i.e. not a virtual port) between the
+ * output and the primary device must support FEC
+ */
+ if (!drm_dp_mst_is_virtual_dpcd(fec_port) &&
+ !fec_port->fec_capable)
+ return NULL;
+
+ fec_port = fec_port->parent->port_parent;
+ }
+
+ /* DP-to-DP peer device */
+ if (drm_dp_mst_is_virtual_dpcd(immediate_upstream_port)) {
+ u8 upstream_dsc;
+
+ if (drm_dp_dpcd_read(&port->aux,
+ DP_DSC_SUPPORT, &endpoint_dsc, 1) != 1)
+ return NULL;
+ if (drm_dp_dpcd_read(&port->aux,
+ DP_FEC_CAPABILITY, &endpoint_fec, 1) != 1)
+ return NULL;
+ if (drm_dp_dpcd_read(&immediate_upstream_port->aux,
+ DP_DSC_SUPPORT, &upstream_dsc, 1) != 1)
+ return NULL;
+
+ /* Enpoint decompression with DP-to-DP peer device */
+ if ((endpoint_dsc & DP_DSC_DECOMPRESSION_IS_SUPPORTED) &&
+ (endpoint_fec & DP_FEC_CAPABLE) &&
+ (upstream_dsc & 0x2) /* DSC passthrough */)
+ return &port->aux;
+
+ /* Virtual DPCD decompression with DP-to-DP peer device */
+ return &immediate_upstream_port->aux;
+ }
+
+ /* Virtual DPCD decompression with DP-to-HDMI or Virtual DP Sink */
+ if (drm_dp_mst_is_virtual_dpcd(port))
+ return &port->aux;
+
+ /*
+ * Synaptics quirk
+ * Applies to ports for which:
+ * - Physical aux has Synaptics OUI
+ * - DPv1.4 or higher
+ * - Port is on primary branch device
+ * - Not a VGA adapter (DP_DWN_STRM_PORT_TYPE_ANALOG)
+ */
+ if (drm_dp_read_desc(port->mgr->aux, &desc, true))
+ return NULL;
+
+ if (drm_dp_has_quirk(&desc, DP_DPCD_QUIRK_DSC_WITHOUT_VIRTUAL_DPCD) &&
+ port->mgr->dpcd[DP_DPCD_REV] >= DP_DPCD_REV_14 &&
+ port->parent == port->mgr->mst_primary) {
+ u8 dpcd_ext[DP_RECEIVER_CAP_SIZE];
+
+ if (drm_dp_read_dpcd_caps(port->mgr->aux, dpcd_ext) < 0)
+ return NULL;
+
+ if ((dpcd_ext[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_PRESENT) &&
+ ((dpcd_ext[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_TYPE_MASK)
+ != DP_DWN_STRM_PORT_TYPE_ANALOG))
+ return port->mgr->aux;
+ }
+
+ /*
+ * The check below verifies if the MST sink
+ * connected to the GPU is capable of DSC -
+ * therefore the endpoint needs to be
+ * both DSC and FEC capable.
+ */
+ if (drm_dp_dpcd_read(&port->aux,
+ DP_DSC_SUPPORT, &endpoint_dsc, 1) != 1)
+ return NULL;
+ if (drm_dp_dpcd_read(&port->aux,
+ DP_FEC_CAPABILITY, &endpoint_fec, 1) != 1)
+ return NULL;
+ if ((endpoint_dsc & DP_DSC_DECOMPRESSION_IS_SUPPORTED) &&
+ (endpoint_fec & DP_FEC_CAPABLE))
+ return &port->aux;
+
+ return NULL;
+ }
+ EXPORT_SYMBOL(drm_dp_mst_dsc_aux_for_port);
unsigned long long output;
acpi_status status;
+ if (acpi_disabled)
+ return false;
+
/* Get embedded-controller handle */
status = acpi_get_devices("PNP0C09", acpi_set_handle, NULL, &ec_handle);
if (ACPI_FAILURE(status) || !ec_handle)
}
#endif
+ #if IS_ENABLED(CONFIG_CHROMEOS_PRIVACY_SCREEN)
+ static bool __init detect_chromeos_privacy_screen(void)
+ {
+ return acpi_dev_present("GOOG0010", NULL, -1);
+ }
+ #endif
+
static const struct arch_init_data arch_init_data[] __initconst = {
#if IS_ENABLED(CONFIG_THINKPAD_ACPI)
{
.detect = detect_thinkpad_privacy_screen,
},
#endif
+ #if IS_ENABLED(CONFIG_CHROMEOS_PRIVACY_SCREEN)
+ {
+ .lookup = {
+ .dev_id = NULL,
+ .con_id = NULL,
+ .provider = "privacy_screen-GOOG0010:00",
+ },
+ .detect = detect_chromeos_privacy_screen,
+ },
+ #endif
};
void __init drm_privacy_screen_lookup_init(void)
continue;
if (bo->flags & ETNA_SUBMIT_BO_WRITE) {
- ret = dma_resv_get_fences(robj, NULL,
- &bo->nr_shared,
+ ret = dma_resv_get_fences(robj, true, &bo->nr_shared,
&bo->shared);
if (ret)
return ret;
return -EINVAL;
}
+ if (args->stream_size > SZ_128K || args->nr_relocs > SZ_128K ||
+ args->nr_bos > SZ_128K || args->nr_pmrs > 128) {
+ DRM_ERROR("submit arguments out of size limits\n");
+ return -EINVAL;
+ }
+
/*
* Copy the command submission and bo array to kernel space in
* one go, and do this outside of any locks.
bool "Exynos specific extensions for Analogix DP driver"
depends on DRM_EXYNOS_FIMD || DRM_EXYNOS7_DECON
select DRM_ANALOGIX_DP
++ select DRM_DP_HELPER
default DRM_EXYNOS
select DRM_PANEL
help
#include <linux/gpio/consumer.h>
#include <linux/irq.h>
#include <linux/of_device.h>
-#include <linux/of_gpio.h>
#include <linux/of_graph.h>
#include <linux/phy/phy.h>
#include <linux/regulator/consumer.h>
struct list_head bridge_chain;
struct drm_bridge *out_bridge;
struct device *dev;
+ struct drm_display_mode mode;
void __iomem *reg_base;
struct phy *phy;
struct clk **clks;
struct regulator_bulk_data supplies[2];
int irq;
- int te_gpio;
+ struct gpio_desc *te_gpio;
u32 pll_clk_rate;
u32 burst_clk_rate;
static void exynos_dsi_set_display_mode(struct exynos_dsi *dsi)
{
- struct drm_display_mode *m = &dsi->encoder.crtc->state->adjusted_mode;
+ struct drm_display_mode *m = &dsi->mode;
unsigned int num_bits_resol = dsi->driver_data->num_bits_resol;
u32 reg;
{
enable_irq(dsi->irq);
- if (gpio_is_valid(dsi->te_gpio))
- enable_irq(gpio_to_irq(dsi->te_gpio));
+ if (dsi->te_gpio)
+ enable_irq(gpiod_to_irq(dsi->te_gpio));
}
static void exynos_dsi_disable_irq(struct exynos_dsi *dsi)
{
- if (gpio_is_valid(dsi->te_gpio))
- disable_irq(gpio_to_irq(dsi->te_gpio));
+ if (dsi->te_gpio)
+ disable_irq(gpiod_to_irq(dsi->te_gpio));
disable_irq(dsi->irq);
}
int ret;
int te_gpio_irq;
- dsi->te_gpio = of_get_named_gpio(panel->of_node, "te-gpios", 0);
- if (dsi->te_gpio == -ENOENT)
- return 0;
-
- if (!gpio_is_valid(dsi->te_gpio)) {
- ret = dsi->te_gpio;
- dev_err(dsi->dev, "cannot get te-gpios, %d\n", ret);
- goto out;
- }
-
- ret = gpio_request(dsi->te_gpio, "te_gpio");
- if (ret) {
- dev_err(dsi->dev, "gpio request failed with %d\n", ret);
- goto out;
+ dsi->te_gpio = devm_gpiod_get_optional(dsi->dev, "te", GPIOD_IN);
+ if (IS_ERR(dsi->te_gpio)) {
+ dev_err(dsi->dev, "gpio request failed with %ld\n",
+ PTR_ERR(dsi->te_gpio));
+ return PTR_ERR(dsi->te_gpio);
}
- te_gpio_irq = gpio_to_irq(dsi->te_gpio);
+ te_gpio_irq = gpiod_to_irq(dsi->te_gpio);
ret = request_threaded_irq(te_gpio_irq, exynos_dsi_te_irq_handler, NULL,
IRQF_TRIGGER_RISING | IRQF_NO_AUTOEN, "TE", dsi);
if (ret) {
dev_err(dsi->dev, "request interrupt failed with %d\n", ret);
- gpio_free(dsi->te_gpio);
- goto out;
+ gpiod_put(dsi->te_gpio);
+ return ret;
}
-out:
- return ret;
+ return 0;
}
static void exynos_dsi_unregister_te_irq(struct exynos_dsi *dsi)
{
- if (gpio_is_valid(dsi->te_gpio)) {
- free_irq(gpio_to_irq(dsi->te_gpio), dsi);
- gpio_free(dsi->te_gpio);
- dsi->te_gpio = -ENOENT;
+ if (dsi->te_gpio) {
+ free_irq(gpiod_to_irq(dsi->te_gpio), dsi);
+ gpiod_put(dsi->te_gpio);
}
}
pm_runtime_put_sync(dsi->dev);
}
+ static void exynos_dsi_mode_set(struct drm_encoder *encoder,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+ {
+ struct exynos_dsi *dsi = encoder_to_dsi(encoder);
+
+ drm_mode_copy(&dsi->mode, adjusted_mode);
+ }
+
static enum drm_connector_status
exynos_dsi_detect(struct drm_connector *connector, bool force)
{
static const struct drm_encoder_helper_funcs exynos_dsi_encoder_helper_funcs = {
.enable = exynos_dsi_enable,
.disable = exynos_dsi_disable,
+ .mode_set = exynos_dsi_mode_set,
};
MODULE_DEVICE_TABLE(of, exynos_dsi_of_match);
if (!dsi)
return -ENOMEM;
- /* To be checked as invalid one */
- dsi->te_gpio = -ENOENT;
-
init_completion(&dsi->completed);
spin_lock_init(&dsi->transfer_lock);
INIT_LIST_HEAD(&dsi->transfer_list);
i915-y += \
$(gem-y) \
i915_active.o \
- i915_buddy.o \
i915_cmd_parser.o \
+ i915_deps.o \
i915_gem_evict.o \
i915_gem_gtt.o \
i915_gem_ww.o \
display/intel_crt.o \
display/intel_ddi.o \
display/intel_ddi_buf_trans.o \
+ display/intel_display_trace.o \
display/intel_dp.o \
display/intel_dp_aux.o \
display/intel_dp_aux_backlight.o \
*
*/
- #include <drm/drm_dp_helper.h>
+ #include <drm/dp/drm_dp_helper.h>
#include "display/intel_display.h"
#include "display/intel_display_types.h"
[DDC_BUS_DDI_D] = GMBUS_PIN_10_TC2_ICP,
};
+static const u8 adlp_ddc_pin_map[] = {
+ [ICL_DDC_BUS_DDI_A] = GMBUS_PIN_1_BXT,
+ [ICL_DDC_BUS_DDI_B] = GMBUS_PIN_2_BXT,
+ [ADLP_DDC_BUS_PORT_TC1] = GMBUS_PIN_9_TC1_ICP,
+ [ADLP_DDC_BUS_PORT_TC2] = GMBUS_PIN_10_TC2_ICP,
+ [ADLP_DDC_BUS_PORT_TC3] = GMBUS_PIN_11_TC3_ICP,
+ [ADLP_DDC_BUS_PORT_TC4] = GMBUS_PIN_12_TC4_ICP,
+};
+
static u8 map_ddc_pin(struct drm_i915_private *i915, u8 vbt_pin)
{
const u8 *ddc_pin_map;
int n_entries;
- if (IS_ALDERLAKE_S(i915)) {
+ if (IS_ALDERLAKE_P(i915)) {
+ ddc_pin_map = adlp_ddc_pin_map;
+ n_entries = ARRAY_SIZE(adlp_ddc_pin_map);
+ } else if (IS_ALDERLAKE_S(i915)) {
ddc_pin_map = adls_ddc_pin_map;
n_entries = ARRAY_SIZE(adls_ddc_pin_map);
} else if (INTEL_PCH_TYPE(i915) >= PCH_DG1) {
#include <linux/module.h>
#include <linux/dma-resv.h>
#include <linux/slab.h>
+#include <linux/vga_switcheroo.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_atomic_uapi.h>
#include <drm/drm_damage_helper.h>
- #include <drm/drm_dp_helper.h>
+ #include <drm/dp/drm_dp_helper.h>
#include <drm/drm_edid.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_plane_helper.h>
+#include <drm/drm_privacy_screen_consumer.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_rect.h>
* the highest stride limits of them all,
* if in case pipe A is disabled, use the first pipe from pipe_mask.
*/
- crtc = intel_get_first_crtc(dev_priv);
+ crtc = intel_first_crtc(dev_priv);
if (!crtc)
return 0;
*/
if (HAS_GMCH(dev_priv) &&
intel_set_memory_cxsr(dev_priv, false))
- intel_wait_for_vblank(dev_priv, crtc->pipe);
+ intel_crtc_wait_for_next_vblank(crtc);
/*
* Gen2 reports pipe underruns whenever all planes are disabled.
intel_set_cpu_fifo_underrun_reporting(dev_priv, crtc->pipe, false);
intel_plane_disable_arm(plane, crtc_state);
- intel_wait_for_vblank(dev_priv, crtc->pipe);
+ intel_crtc_wait_for_next_vblank(crtc);
}
unsigned int
static bool gpu_reset_clobbers_display(struct drm_i915_private *dev_priv)
{
return (INTEL_INFO(dev_priv)->gpu_reset_clobbers_display &&
- intel_has_gpu_reset(&dev_priv->gt));
+ intel_has_gpu_reset(to_gt(dev_priv)));
}
void intel_display_prepare_reset(struct drm_i915_private *dev_priv)
return;
/* We have a modeset vs reset deadlock, defensively unbreak it. */
- set_bit(I915_RESET_MODESET, &dev_priv->gt.reset.flags);
+ set_bit(I915_RESET_MODESET, &to_gt(dev_priv)->reset.flags);
smp_mb__after_atomic();
- wake_up_bit(&dev_priv->gt.reset.flags, I915_RESET_MODESET);
+ wake_up_bit(&to_gt(dev_priv)->reset.flags, I915_RESET_MODESET);
if (atomic_read(&dev_priv->gpu_error.pending_fb_pin)) {
drm_dbg_kms(&dev_priv->drm,
"Modeset potentially stuck, unbreaking through wedging\n");
- intel_gt_set_wedged(&dev_priv->gt);
+ intel_gt_set_wedged(to_gt(dev_priv));
}
/*
return;
/* reset doesn't touch the display */
- if (!test_bit(I915_RESET_MODESET, &dev_priv->gt.reset.flags))
+ if (!test_bit(I915_RESET_MODESET, &to_gt(dev_priv)->reset.flags))
return;
state = fetch_and_zero(&dev_priv->modeset_restore_state);
drm_modeset_acquire_fini(ctx);
mutex_unlock(&dev->mode_config.mutex);
- clear_bit_unlock(I915_RESET_MODESET, &dev_priv->gt.reset.flags);
+ clear_bit_unlock(I915_RESET_MODESET, &to_gt(dev_priv)->reset.flags);
}
static void icl_set_pipe_chicken(const struct intel_crtc_state *crtc_state)
else if (DISPLAY_VER(dev_priv) >= 13)
tmp |= UNDERRUN_RECOVERY_DISABLE_ADLP;
+ /* Wa_14010547955:dg2 */
+ if (IS_DG2_DISPLAY_STEP(dev_priv, STEP_B0, STEP_FOREVER))
+ tmp |= DG2_RENDER_CCSTAG_4_3_EN;
+
intel_de_write(dev_priv, PIPE_CHICKEN(pipe), tmp);
}
if (cleanup_done)
continue;
- drm_crtc_wait_one_vblank(crtc);
+ intel_crtc_wait_for_next_vblank(to_intel_crtc(crtc));
return true;
}
}
/* We need to wait for a vblank before we can disable the plane. */
- intel_wait_for_vblank(dev_priv, crtc->pipe);
+ intel_crtc_wait_for_next_vblank(crtc);
}
static void intel_crtc_dpms_overlay_disable(struct intel_crtc *crtc)
static void intel_crtc_async_flip_disable_wa(struct intel_atomic_state *state,
struct intel_crtc *crtc)
{
- struct drm_i915_private *i915 = to_i915(state->base.dev);
const struct intel_crtc_state *old_crtc_state =
intel_atomic_get_old_crtc_state(state, crtc);
const struct intel_crtc_state *new_crtc_state =
}
if (need_vbl_wait)
- intel_wait_for_vblank(i915, crtc->pipe);
+ intel_crtc_wait_for_next_vblank(crtc);
}
static void intel_pre_plane_update(struct intel_atomic_state *state,
hsw_disable_ips(old_crtc_state);
if (intel_fbc_pre_update(state, crtc))
- intel_wait_for_vblank(dev_priv, pipe);
+ intel_crtc_wait_for_next_vblank(crtc);
if (!needs_async_flip_vtd_wa(old_crtc_state) &&
needs_async_flip_vtd_wa(new_crtc_state))
*/
if (HAS_GMCH(dev_priv) && old_crtc_state->hw.active &&
new_crtc_state->disable_cxsr && intel_set_memory_cxsr(dev_priv, false))
- intel_wait_for_vblank(dev_priv, pipe);
+ intel_crtc_wait_for_next_vblank(crtc);
/*
* IVB workaround: must disable low power watermarks for at least
*/
if (old_crtc_state->hw.active &&
new_crtc_state->disable_lp_wm && ilk_disable_lp_wm(dev_priv))
- intel_wait_for_vblank(dev_priv, pipe);
+ intel_crtc_wait_for_next_vblank(crtc);
/*
* If we're doing a modeset we don't need to do any
* in case there are more corner cases we don't know about.
*/
if (new_crtc_state->has_pch_encoder) {
- intel_wait_for_vblank(dev_priv, pipe);
- intel_wait_for_vblank(dev_priv, pipe);
+ intel_crtc_wait_for_next_vblank(crtc);
+ intel_crtc_wait_for_next_vblank(crtc);
}
intel_set_cpu_fifo_underrun_reporting(dev_priv, pipe, true);
intel_set_pch_fifo_underrun_reporting(dev_priv, pipe, true);
intel_encoders_enable(state, crtc);
if (psl_clkgate_wa) {
- intel_wait_for_vblank(dev_priv, pipe);
+ intel_crtc_wait_for_next_vblank(crtc);
glk_pipe_scaler_clock_gating_wa(dev_priv, pipe, false);
}
* to change the workaround. */
hsw_workaround_pipe = new_crtc_state->hsw_workaround_pipe;
if (IS_HASWELL(dev_priv) && hsw_workaround_pipe != INVALID_PIPE) {
- intel_wait_for_vblank(dev_priv, hsw_workaround_pipe);
- intel_wait_for_vblank(dev_priv, hsw_workaround_pipe);
+ struct intel_crtc *wa_crtc;
+
+ wa_crtc = intel_crtc_for_pipe(dev_priv, hsw_workaround_pipe);
+
+ intel_crtc_wait_for_next_vblank(wa_crtc);
+ intel_crtc_wait_for_next_vblank(wa_crtc);
}
}
/* prevents spurious underruns */
if (DISPLAY_VER(dev_priv) == 2)
- intel_wait_for_vblank(dev_priv, pipe);
+ intel_crtc_wait_for_next_vblank(crtc);
}
static void i9xx_pfit_disable(const struct intel_crtc_state *old_crtc_state)
* wait for planes to fully turn off before disabling the pipe.
*/
if (DISPLAY_VER(dev_priv) == 2)
- intel_wait_for_vblank(dev_priv, pipe);
+ intel_crtc_wait_for_next_vblank(crtc);
intel_encoders_disable(state, crtc);
drm_atomic_state_put(state);
/* let the connector get through one full cycle before testing */
- intel_wait_for_vblank(dev_priv, crtc->pipe);
+ intel_crtc_wait_for_next_vblank(crtc);
+
return true;
fail:
if (!encoder->get_hw_state(encoder, &pipe))
return NULL;
- crtc = intel_get_crtc_for_pipe(dev_priv, pipe);
+ crtc = intel_crtc_for_pipe(dev_priv, pipe);
mode = kzalloc(sizeof(*mode), GFP_KERNEL);
if (!mode)
if (icl_is_hdr_plane(dev_priv, plane->id)) {
if (linked->id == PLANE_SPRITE5)
- plane_state->cus_ctl |= PLANE_CUS_PLANE_7;
+ plane_state->cus_ctl |= PLANE_CUS_Y_PLANE_7_ICL;
else if (linked->id == PLANE_SPRITE4)
- plane_state->cus_ctl |= PLANE_CUS_PLANE_6;
+ plane_state->cus_ctl |= PLANE_CUS_Y_PLANE_6_ICL;
else if (linked->id == PLANE_SPRITE3)
- plane_state->cus_ctl |= PLANE_CUS_PLANE_5_RKL;
+ plane_state->cus_ctl |= PLANE_CUS_Y_PLANE_5_RKL;
else if (linked->id == PLANE_SPRITE2)
- plane_state->cus_ctl |= PLANE_CUS_PLANE_4_RKL;
+ plane_state->cus_ctl |= PLANE_CUS_Y_PLANE_4_RKL;
else
MISSING_CASE(linked->id);
}
return 0;
}
-static int intel_atomic_check_cdclk(struct intel_atomic_state *state,
- bool *need_cdclk_calc)
-{
- struct drm_i915_private *dev_priv = to_i915(state->base.dev);
- const struct intel_cdclk_state *old_cdclk_state;
- const struct intel_cdclk_state *new_cdclk_state;
- struct intel_plane_state *plane_state;
- struct intel_bw_state *new_bw_state;
- struct intel_plane *plane;
- int min_cdclk = 0;
- enum pipe pipe;
- int ret;
- int i;
- /*
- * active_planes bitmask has been updated, and potentially
- * affected planes are part of the state. We can now
- * compute the minimum cdclk for each plane.
- */
- for_each_new_intel_plane_in_state(state, plane, plane_state, i) {
- ret = intel_plane_calc_min_cdclk(state, plane, need_cdclk_calc);
- if (ret)
- return ret;
- }
-
- old_cdclk_state = intel_atomic_get_old_cdclk_state(state);
- new_cdclk_state = intel_atomic_get_new_cdclk_state(state);
-
- if (new_cdclk_state &&
- old_cdclk_state->force_min_cdclk != new_cdclk_state->force_min_cdclk)
- *need_cdclk_calc = true;
-
- ret = intel_cdclk_bw_calc_min_cdclk(state);
- if (ret)
- return ret;
-
- new_bw_state = intel_atomic_get_new_bw_state(state);
-
- if (!new_cdclk_state || !new_bw_state)
- return 0;
-
- for_each_pipe(dev_priv, pipe) {
- min_cdclk = max(new_cdclk_state->min_cdclk[pipe], min_cdclk);
-
- /*
- * Currently do this change only if we need to increase
- */
- if (new_bw_state->min_cdclk > min_cdclk)
- *need_cdclk_calc = true;
- }
-
- return 0;
-}
-
static int intel_atomic_check_crtcs(struct intel_atomic_state *state)
{
struct intel_crtc_state *crtc_state;
if (ret)
goto fail;
- intel_fbc_choose_crtc(dev_priv, state);
ret = intel_compute_global_watermarks(state);
if (ret)
goto fail;
if (ret)
goto fail;
- ret = intel_atomic_check_cdclk(state, &any_ms);
+ ret = intel_cdclk_atomic_check(state, &any_ms);
if (ret)
goto fail;
if (ret)
goto fail;
+ ret = intel_fbc_atomic_check(state);
+ if (ret)
+ goto fail;
+
for_each_oldnew_intel_crtc_in_state(state, crtc, old_crtc_state,
new_crtc_state, i) {
if (new_crtc_state->uapi.async_flip) {
if (!skl_ddb_entry_equal(&new_crtc_state->wm.skl.ddb,
&old_crtc_state->wm.skl.ddb) &&
(update_pipes | modeset_pipes))
- intel_wait_for_vblank(dev_priv, pipe);
+ intel_crtc_wait_for_next_vblank(crtc);
}
}
for (;;) {
prepare_to_wait(&intel_state->commit_ready.wait,
&wait_fence, TASK_UNINTERRUPTIBLE);
- prepare_to_wait(bit_waitqueue(&dev_priv->gt.reset.flags,
+ prepare_to_wait(bit_waitqueue(&to_gt(dev_priv)->reset.flags,
I915_RESET_MODESET),
&wait_reset, TASK_UNINTERRUPTIBLE);
if (i915_sw_fence_done(&intel_state->commit_ready) ||
- test_bit(I915_RESET_MODESET, &dev_priv->gt.reset.flags))
+ test_bit(I915_RESET_MODESET, &to_gt(dev_priv)->reset.flags))
break;
schedule();
}
finish_wait(&intel_state->commit_ready.wait, &wait_fence);
- finish_wait(bit_waitqueue(&dev_priv->gt.reset.flags,
+ finish_wait(bit_waitqueue(&to_gt(dev_priv)->reset.flags,
I915_RESET_MODESET),
&wait_reset);
}
struct intel_plane *plane;
for_each_intel_plane(&dev_priv->drm, plane) {
- struct intel_crtc *crtc = intel_get_crtc_for_pipe(dev_priv,
- plane->pipe);
+ struct intel_crtc *crtc = intel_crtc_for_pipe(dev_priv,
+ plane->pipe);
plane->base.possible_crtcs = drm_crtc_mask(&crtc->base);
}
void i830_enable_pipe(struct drm_i915_private *dev_priv, enum pipe pipe)
{
- struct intel_crtc *crtc = intel_get_crtc_for_pipe(dev_priv, pipe);
+ struct intel_crtc *crtc = intel_crtc_for_pipe(dev_priv, pipe);
/* 640x480@60Hz, ~25175 kHz */
struct dpll clock = {
.m1 = 18,
void i830_disable_pipe(struct drm_i915_private *dev_priv, enum pipe pipe)
{
- struct intel_crtc *crtc = intel_get_crtc_for_pipe(dev_priv, pipe);
+ struct intel_crtc *crtc = intel_crtc_for_pipe(dev_priv, pipe);
drm_dbg_kms(&dev_priv->drm, "disabling pipe %c due to force quirk\n",
pipe_name(pipe));
"[PLANE:%d:%s] attached to the wrong pipe, disabling plane\n",
plane->base.base.id, plane->base.name);
- plane_crtc = intel_get_crtc_for_pipe(dev_priv, pipe);
+ plane_crtc = intel_crtc_for_pipe(dev_priv, pipe);
intel_plane_disable_noatomic(plane_crtc, plane);
}
}
visible = plane->get_hw_state(plane, &pipe);
- crtc = intel_get_crtc_for_pipe(dev_priv, pipe);
+ crtc = intel_crtc_for_pipe(dev_priv, pipe);
crtc_state = to_intel_crtc_state(crtc->base.state);
intel_set_plane_visible(crtc_state, plane_state, visible);
pipe = 0;
if (encoder->get_hw_state(encoder, &pipe)) {
- crtc = intel_get_crtc_for_pipe(dev_priv, pipe);
+ crtc = intel_crtc_for_pipe(dev_priv, pipe);
crtc_state = to_intel_crtc_state(crtc->base.state);
encoder->base.crtc = &crtc->base;
intel_bios_driver_remove(i915);
}
+bool intel_modeset_probe_defer(struct pci_dev *pdev)
+{
+ struct drm_privacy_screen *privacy_screen;
+
+ /*
+ * apple-gmux is needed on dual GPU MacBook Pro
+ * to probe the panel if we're the inactive GPU.
+ */
+ if (vga_switcheroo_client_probe_defer(pdev))
+ return true;
+
+ /* If the LCD panel has a privacy-screen, wait for it */
+ privacy_screen = drm_privacy_screen_get(&pdev->dev, NULL);
+ if (IS_ERR(privacy_screen) && PTR_ERR(privacy_screen) == -EPROBE_DEFER)
+ return true;
+
+ drm_privacy_screen_put(privacy_screen);
+
+ return false;
+}
+
void intel_display_driver_register(struct drm_i915_private *i915)
{
if (!HAS_DISPLAY(i915))
#include <linux/pwm.h>
#include <linux/sched/clock.h>
+ #include <drm/dp/drm_dp_dual_mode_helper.h>
+ #include <drm/dp/drm_dp_mst_helper.h>
#include <drm/drm_atomic.h>
#include <drm/drm_crtc.h>
- #include <drm/drm_dp_dual_mode_helper.h>
- #include <drm/drm_dp_mst_helper.h>
+#include <drm/drm_dsc.h>
#include <drm/drm_encoder.h>
#include <drm/drm_fb_helper.h>
#include <drm/drm_fourcc.h>
#include <drm/i915_mei_hdcp_interface.h>
#include <media/cec-notifier.h>
-#include "i915_drv.h"
+#include "i915_vma.h"
+#include "i915_vma_types.h"
+#include "intel_bios.h"
+#include "intel_display.h"
+#include "intel_display_power.h"
+#include "intel_dpll_mgr.h"
+#include "intel_pm_types.h"
struct drm_printer;
struct __intel_global_objs_state;
struct intel_ddi_buf_trans;
struct intel_fbc;
+struct intel_connector;
/*
* Display related stuff
/* Clear Color Value */
u64 ccval;
+
+ const char *no_fbc_reason;
};
struct intel_initial_plane_config {
bool crc_enabled;
- bool enable_fbc;
-
bool double_wide;
int pbn;
}
}
-static inline bool intel_pipe_valid(struct drm_i915_private *i915, enum pipe pipe)
-{
- return (pipe >= 0 &&
- pipe < ARRAY_SIZE(i915->pipe_to_crtc_mapping) &&
- INTEL_INFO(i915)->pipe_mask & BIT(pipe) &&
- i915->pipe_to_crtc_mapping[pipe]);
-}
-
-static inline struct intel_crtc *
-intel_get_first_crtc(struct drm_i915_private *dev_priv)
-{
- return to_intel_crtc(drm_crtc_from_index(&dev_priv->drm, 0));
-}
-
-static inline struct intel_crtc *
-intel_get_crtc_for_pipe(struct drm_i915_private *dev_priv, enum pipe pipe)
-{
- /* pipe_to_crtc_mapping may have hole on any of 3 display pipe system */
- drm_WARN_ON(&dev_priv->drm,
- !(INTEL_INFO(dev_priv)->pipe_mask & BIT(pipe)));
- return dev_priv->pipe_to_crtc_mapping[pipe];
-}
-
-static inline struct intel_crtc *
-intel_get_crtc_for_plane(struct drm_i915_private *dev_priv, enum i9xx_plane_id plane)
-{
- return dev_priv->plane_to_crtc_mapping[plane];
-}
-
struct intel_load_detect_pipe {
struct drm_atomic_state *restore_state;
};
return &dp_to_dig_port(intel_dp)->lspcon;
}
-static inline struct drm_i915_private *
-dp_to_i915(struct intel_dp *intel_dp)
-{
- return to_i915(dp_to_dig_port(intel_dp)->base.base.dev);
-}
+#define dp_to_i915(__intel_dp) to_i915(dp_to_dig_port(__intel_dp)->base.base.dev)
#define CAN_PSR(intel_dp) ((intel_dp)->psr.sink_support && \
(intel_dp)->psr.source_support)
return drm_atomic_crtc_needs_modeset(&crtc_state->uapi);
}
-static inline void
-intel_wait_for_vblank(struct drm_i915_private *dev_priv, enum pipe pipe)
-{
- struct intel_crtc *crtc = intel_get_crtc_for_pipe(dev_priv, pipe);
-
- drm_crtc_wait_one_vblank(&crtc->base);
-}
-
-static inline void
-intel_wait_for_vblank_if_active(struct drm_i915_private *dev_priv, enum pipe pipe)
-{
- const struct intel_crtc *crtc = intel_get_crtc_for_pipe(dev_priv, pipe);
-
- if (crtc->active)
- intel_wait_for_vblank(dev_priv, pipe);
-}
-
-static inline bool intel_modifier_uses_dpt(struct drm_i915_private *i915, u64 modifier)
-{
- return DISPLAY_VER(i915) >= 13 && modifier != DRM_FORMAT_MOD_LINEAR;
-}
-
-static inline bool intel_fb_uses_dpt(const struct drm_framebuffer *fb)
-{
- return fb && intel_modifier_uses_dpt(to_i915(fb->dev), fb->modifier);
-}
-
static inline u32 intel_plane_ggtt_offset(const struct intel_plane_state *plane_state)
{
return i915_ggtt_offset(plane_state->ggtt_vma);
#include <drm/drm_atomic_helper.h>
#include <drm/drm_crtc.h>
- #include <drm/drm_dp_helper.h>
+ #include <drm/dp/drm_dp_helper.h>
#include <drm/drm_edid.h>
#include <drm/drm_probe_helper.h>
#include "intel_audio.h"
#include "intel_backlight.h"
#include "intel_connector.h"
+#include "intel_crtc.h"
#include "intel_ddi.h"
#include "intel_de.h"
#include "intel_display_types.h"
to_intel_crtc_state(crtc->base.state);
/* Keep underrun reporting disabled until things are stable */
- intel_wait_for_vblank(dev_priv, crtc->pipe);
+ intel_crtc_wait_for_next_vblank(crtc);
intel_set_cpu_fifo_underrun_reporting(dev_priv, crtc->pipe, true);
if (crtc_state->has_pch_encoder)
#include <linux/prime_numbers.h>
#include <linux/sort.h>
+ #include <drm/drm_buddy.h>
+
#include "../i915_selftest.h"
#include "mock_drm.h"
#include "gt/intel_engine_pm.h"
#include "gt/intel_engine_user.h"
#include "gt/intel_gt.h"
- #include "i915_buddy.h"
#include "gt/intel_migrate.h"
#include "i915_memcpy.h"
#include "i915_ttm_buddy_manager.h"
struct drm_i915_private *i915 = mem->i915;
struct i915_ttm_buddy_resource *res;
struct drm_i915_gem_object *obj;
- struct i915_buddy_mm *mm;
+ struct drm_buddy *mm;
unsigned int expected_order;
LIST_HEAD(objects);
u64 size;
struct drm_i915_private *i915 = mem->i915;
struct i915_ttm_buddy_resource *res;
struct drm_i915_gem_object *obj;
- struct i915_buddy_block *block;
- struct i915_buddy_mm *mm;
+ struct drm_buddy_block *block;
+ struct drm_buddy *mm;
struct list_head *blocks;
struct scatterlist *sg;
LIST_HEAD(objects);
mm = res->mm;
size = 0;
list_for_each_entry(block, blocks, link) {
- if (i915_buddy_block_size(mm, block) > size)
- size = i915_buddy_block_size(mm, block);
+ if (drm_buddy_block_size(mm, block) > size)
+ size = drm_buddy_block_size(mm, block);
}
if (size < max_segment) {
pr_err("%s: Failed to create a huge contiguous block [> %u], largest block %lld\n",
return 0;
}
- if (intel_gt_is_wedged(&i915->gt))
+ if (intel_gt_is_wedged(to_gt(i915)))
return 0;
return i915_live_subtests(tests, i915);
SUBTEST(perf_memcpy),
};
- if (intel_gt_is_wedged(&i915->gt))
+ if (intel_gt_is_wedged(to_gt(i915)))
return 0;
return i915_live_subtests(tests, i915);
select IOMMU_IO_PGTABLE
select QCOM_MDT_LOADER if ARCH_QCOM
select REGULATOR
+ select DRM_DP_HELPER
select DRM_KMS_HELPER
select DRM_PANEL
select DRM_BRIDGE
config DRM_MSM_DP
bool "Enable DisplayPort support in MSM DRM driver"
depends on DRM_MSM
+ select RATIONAL
default y
help
Compile in support for DP driver in MSM DRM driver. DP external
#include <linux/phy/phy-dp.h>
#include <linux/pm_opp.h>
#include <drm/drm_fixed.h>
- #include <drm/drm_dp_helper.h>
+ #include <drm/dp/drm_dp_helper.h>
#include <drm/drm_print.h>
#include "dp_reg.h"
static void dp_ctrl_config_ctrl(struct dp_ctrl_private *ctrl)
{
u32 config = 0, tbd;
- u8 *dpcd = ctrl->panel->dpcd;
+ const u8 *dpcd = ctrl->panel->dpcd;
/* Default-> LSCLK DIV: 1/4 LCLK */
config |= (2 << DP_CONFIGURATION_CTRL_LSCLK_DIV_SHIFT);
/* Scrambler reset enable */
- if (dpcd[DP_EDP_CONFIGURATION_CAP] & DP_ALTERNATE_SCRAMBLER_RESET_CAP)
+ if (drm_dp_alternate_scrambler_reset_cap(dpcd))
config |= DP_CONFIGURATION_CTRL_ASSR;
tbd = dp_link_get_test_bits_depth(ctrl->link,
int *training_step)
{
int ret = 0;
+ const u8 *dpcd = ctrl->panel->dpcd;
u8 encoding = DP_SET_ANSI_8B10B;
+ u8 ssc;
+ u8 assr;
struct dp_link_info link_info = {0};
dp_ctrl_config_ctrl(ctrl);
link_info.capabilities = DP_LINK_CAP_ENHANCED_FRAMING;
dp_aux_link_configure(ctrl->aux, &link_info);
+
+ if (drm_dp_max_downspread(dpcd)) {
+ ssc = DP_SPREAD_AMP_0_5;
+ drm_dp_dpcd_write(ctrl->aux, DP_DOWNSPREAD_CTRL, &ssc, 1);
+ }
+
drm_dp_dpcd_write(ctrl->aux, DP_MAIN_LINK_CHANNEL_CODING_SET,
&encoding, 1);
+ if (drm_dp_alternate_scrambler_reset_cap(dpcd)) {
+ assr = DP_ALTERNATE_SCRAMBLER_RESET_ENABLE;
+ drm_dp_dpcd_write(ctrl->aux, DP_EDP_CONFIGURATION_SET,
+ &assr, 1);
+ }
+
ret = dp_ctrl_link_train_1(ctrl, training_step);
if (ret) {
DRM_ERROR("link training #1 failed. ret=%d\n", ret);
struct dp_io *dp_io = &ctrl->parser->io;
struct phy *phy = dp_io->phy;
struct phy_configure_opts_dp *opts_dp = &dp_io->phy_opts.dp;
+ const u8 *dpcd = ctrl->panel->dpcd;
opts_dp->lanes = ctrl->link->link_params.num_lanes;
opts_dp->link_rate = ctrl->link->link_params.rate / 100;
+ opts_dp->ssc = drm_dp_max_downspread(dpcd);
dp_ctrl_set_clock_rate(ctrl, DP_CTRL_PM, "ctrl_link",
ctrl->link->link_params.rate * 1000);
static bool dp_ctrl_use_fixed_nvid(struct dp_ctrl_private *ctrl)
{
- u8 *dpcd = ctrl->panel->dpcd;
+ const u8 *dpcd = ctrl->panel->dpcd;
/*
* For better interop experience, used a fixed NVID=0x8000
config DRM_ROCKCHIP
tristate "DRM Support for Rockchip"
depends on DRM && ROCKCHIP_IOMMU
+ select DRM_DP_HELPER
select DRM_GEM_CMA_HELPER
select DRM_KMS_HELPER
select DRM_PANEL
select VIDEOMODE_HELPERS
select DRM_ANALOGIX_DP if ROCKCHIP_ANALOGIX_DP
++ select DRM_DP_HELPER if ROCKCHIP_ANALOGIX_DP
select DRM_DW_HDMI if ROCKCHIP_DW_HDMI
select DRM_DW_MIPI_DSI if ROCKCHIP_DW_MIPI_DSI
select GENERIC_PHY if ROCKCHIP_DW_MIPI_DSI
depends on COMMON_CLK
depends on DRM
depends on OF
+ select DRM_DP_HELPER
select DRM_KMS_HELPER
select DRM_MIPI_DSI
select DRM_PANEL
select INTERCONNECT
select IOMMU_IOVA
select CEC_CORE if CEC_NOTIFIER
+ select SND_SIMPLE_CARD if SND_SOC_TEGRA20_SPDIF
+ select SND_SOC_HDMI_CODEC if SND_SOC_TEGRA20_SPDIF
+ select SND_AUDIO_GRAPH_CARD if SND_SOC_TEGRA20_SPDIF
help
Choose this option if you have an NVIDIA Tegra SoC.
{
struct drm_display_mode mode = { SIMPLEDRM_MODE(width, height) };
- mode.clock = 60 /* Hz */ * mode.hdisplay * mode.vdisplay;
+ mode.clock = mode.hdisplay * mode.vdisplay * 60 / 1000 /* kHz */;
drm_mode_set_name(&mode);
return mode;
{
struct drm_device *dev = &sdev->dev;
struct platform_device *pdev = sdev->pdev;
- struct resource *mem;
+ struct resource *res, *mem;
void __iomem *screen_base;
int ret;
- mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- if (!mem)
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
return -EINVAL;
- ret = devm_aperture_acquire_from_firmware(dev, mem->start, resource_size(mem));
+ ret = devm_aperture_acquire_from_firmware(dev, res->start, resource_size(res));
if (ret) {
drm_err(dev, "could not acquire memory range %pr: error %d\n",
- mem, ret);
+ res, ret);
return ret;
}
+ mem = devm_request_mem_region(&pdev->dev, res->start, resource_size(res),
+ sdev->dev.driver->name);
+ if (!mem) {
+ /*
+ * We cannot make this fatal. Sometimes this comes from magic
+ * spaces our resource handlers simply don't know about. Use
+ * the I/O-memory resource as-is and try to map that instead.
+ */
+ drm_warn(dev, "could not acquire memory region %pr\n", res);
+ mem = res;
+ }
+
screen_base = devm_ioremap_wc(&pdev->dev, mem->start,
resource_size(mem));
if (!screen_base)
u32 battery_force_primary:1;
u32 input_device_registered:1;
u32 platform_drv_registered:1;
- u32 platform_drv_attrs_registered:1;
u32 sensors_pdrv_registered:1;
- u32 sensors_pdrv_attrs_registered:1;
- u32 sensors_pdev_attrs_registered:1;
u32 hotkey_poll_active:1;
u32 has_adaptive_kbd:1;
+ u32 kbd_lang:1;
} tp_features;
static struct {
static int dispatch_proc_open(struct inode *inode, struct file *file)
{
- return single_open(file, dispatch_proc_show, PDE_DATA(inode));
+ return single_open(file, dispatch_proc_show, pde_data(inode));
}
static ssize_t dispatch_proc_write(struct file *file,
const char __user *userbuf,
size_t count, loff_t *pos)
{
- struct ibm_struct *ibm = PDE_DATA(file_inode(file));
+ struct ibm_struct *ibm = pde_data(file_inode(file));
char *kernbuf;
int ret;
}
}
-static struct platform_driver tpacpi_pdriver = {
- .driver = {
- .name = TPACPI_DRVR_NAME,
- .pm = &tpacpi_pm,
- },
- .shutdown = tpacpi_shutdown_handler,
-};
-
-static struct platform_driver tpacpi_hwmon_pdriver = {
- .driver = {
- .name = TPACPI_HWMON_DRVR_NAME,
- },
-};
-
/*************************************************************************
* sysfs support helpers
*/
static DRIVER_ATTR_RW(uwb_emulstate);
#endif
-/* --------------------------------------------------------------------- */
-
-static struct driver_attribute *tpacpi_driver_attributes[] = {
- &driver_attr_debug_level, &driver_attr_version,
- &driver_attr_interface_version,
-};
-
-static int __init tpacpi_create_driver_attributes(struct device_driver *drv)
-{
- int i, res;
-
- i = 0;
- res = 0;
- while (!res && i < ARRAY_SIZE(tpacpi_driver_attributes)) {
- res = driver_create_file(drv, tpacpi_driver_attributes[i]);
- i++;
- }
-
-#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
- if (!res && dbg_wlswemul)
- res = driver_create_file(drv, &driver_attr_wlsw_emulstate);
- if (!res && dbg_bluetoothemul)
- res = driver_create_file(drv, &driver_attr_bluetooth_emulstate);
- if (!res && dbg_wwanemul)
- res = driver_create_file(drv, &driver_attr_wwan_emulstate);
- if (!res && dbg_uwbemul)
- res = driver_create_file(drv, &driver_attr_uwb_emulstate);
-#endif
-
- return res;
-}
-
-static void tpacpi_remove_driver_attributes(struct device_driver *drv)
-{
- int i;
-
- for (i = 0; i < ARRAY_SIZE(tpacpi_driver_attributes); i++)
- driver_remove_file(drv, tpacpi_driver_attributes[i]);
-
-#ifdef THINKPAD_ACPI_DEBUGFACILITIES
- driver_remove_file(drv, &driver_attr_wlsw_emulstate);
- driver_remove_file(drv, &driver_attr_bluetooth_emulstate);
- driver_remove_file(drv, &driver_attr_wwan_emulstate);
- driver_remove_file(drv, &driver_attr_uwb_emulstate);
-#endif
-}
-
/*************************************************************************
* Firmware Data
*/
NULL
};
+static umode_t hadaptive_kbd_attr_is_visible(struct kobject *kobj,
+ struct attribute *attr, int n)
+{
+ return tp_features.has_adaptive_kbd ? attr->mode : 0;
+}
+
static const struct attribute_group adaptive_kbd_attr_group = {
+ .is_visible = hadaptive_kbd_attr_is_visible,
.attrs = adaptive_kbd_attributes,
};
hotkey_poll_stop_sync();
mutex_unlock(&hotkey_mutex);
#endif
- sysfs_remove_group(&tpacpi_pdev->dev.kobj, &hotkey_attr_group);
-
dbg_printk(TPACPI_DBG_EXIT | TPACPI_DBG_HKEY,
"restoring original HKEY status and mask\n");
/* yes, there is a bitwise or below, we want the
str_supported(tp_features.hotkey));
if (!tp_features.hotkey)
- return 1;
+ return -ENODEV;
quirks = tpacpi_check_quirks(tpacpi_hotkey_qtable,
ARRAY_SIZE(tpacpi_hotkey_qtable));
*/
if (acpi_evalf(hkey_handle, &hotkey_adaptive_all_mask,
"MHKA", "dd", 2)) {
- if (hotkey_adaptive_all_mask != 0) {
+ if (hotkey_adaptive_all_mask != 0)
tp_features.has_adaptive_kbd = true;
- res = sysfs_create_group(
- &tpacpi_pdev->dev.kobj,
- &adaptive_kbd_attr_group);
- if (res)
- goto err_exit;
- }
} else {
tp_features.has_adaptive_kbd = false;
hotkey_adaptive_all_mask = 0x0U;
* the first hotkey_mask_get to return hotkey_orig_mask */
res = hotkey_mask_get();
if (res)
- goto err_exit;
+ return res;
hotkey_orig_mask = hotkey_acpi_mask;
} else {
}
tabletsw_state = hotkey_init_tablet_mode();
- res = sysfs_create_group(&tpacpi_pdev->dev.kobj, &hotkey_attr_group);
- if (res)
- goto err_exit;
/* Set up key map */
keymap_id = tpacpi_check_quirks(tpacpi_keymap_qtable,
TPACPI_HOTKEY_MAP_SIZE, GFP_KERNEL);
if (!hotkey_keycode_map) {
pr_err("failed to allocate memory for key map\n");
- res = -ENOMEM;
- goto err_exit;
+ return -ENOMEM;
}
input_set_capability(tpacpi_inputdev, EV_MSC, MSC_SCAN);
hotkey_poll_setup_safe(true);
return 0;
-
-err_exit:
- sysfs_remove_group(&tpacpi_pdev->dev.kobj, &hotkey_attr_group);
- sysfs_remove_group(&tpacpi_pdev->dev.kobj, &adaptive_kbd_attr_group);
-
- return (res < 0) ? res : 1;
}
/* Thinkpad X1 Carbon support 5 modes including Home mode, Web browser
NULL
};
+static umode_t bluetooth_attr_is_visible(struct kobject *kobj,
+ struct attribute *attr, int n)
+{
+ return tp_features.bluetooth ? attr->mode : 0;
+}
+
static const struct attribute_group bluetooth_attr_group = {
+ .is_visible = bluetooth_attr_is_visible,
.attrs = bluetooth_attributes,
};
static void bluetooth_exit(void)
{
- sysfs_remove_group(&tpacpi_pdev->dev.kobj,
- &bluetooth_attr_group);
-
tpacpi_destroy_rfkill(TPACPI_RFK_BLUETOOTH_SW_ID);
-
bluetooth_shutdown();
}
}
if (!tp_features.bluetooth)
- return 1;
+ return -ENODEV;
res = tpacpi_new_rfkill(TPACPI_RFK_BLUETOOTH_SW_ID,
&bluetooth_tprfk_ops,
RFKILL_TYPE_BLUETOOTH,
TPACPI_RFK_BLUETOOTH_SW_NAME,
true);
- if (res)
- return res;
-
- res = sysfs_create_group(&tpacpi_pdev->dev.kobj,
- &bluetooth_attr_group);
- if (res) {
- tpacpi_destroy_rfkill(TPACPI_RFK_BLUETOOTH_SW_ID);
- return res;
- }
-
- return 0;
+ return res;
}
/* procfs -------------------------------------------------------------- */
NULL
};
+static umode_t wan_attr_is_visible(struct kobject *kobj, struct attribute *attr,
+ int n)
+{
+ return tp_features.wan ? attr->mode : 0;
+}
+
static const struct attribute_group wan_attr_group = {
+ .is_visible = wan_attr_is_visible,
.attrs = wan_attributes,
};
static void wan_exit(void)
{
- sysfs_remove_group(&tpacpi_pdev->dev.kobj,
- &wan_attr_group);
-
tpacpi_destroy_rfkill(TPACPI_RFK_WWAN_SW_ID);
-
wan_shutdown();
}
}
if (!tp_features.wan)
- return 1;
+ return -ENODEV;
res = tpacpi_new_rfkill(TPACPI_RFK_WWAN_SW_ID,
&wan_tprfk_ops,
RFKILL_TYPE_WWAN,
TPACPI_RFK_WWAN_SW_NAME,
true);
- if (res)
- return res;
-
- res = sysfs_create_group(&tpacpi_pdev->dev.kobj,
- &wan_attr_group);
-
- if (res) {
- tpacpi_destroy_rfkill(TPACPI_RFK_WWAN_SW_ID);
- return res;
- }
-
- return 0;
+ return res;
}
/* procfs -------------------------------------------------------------- */
}
if (!tp_features.uwb)
- return 1;
+ return -ENODEV;
res = tpacpi_new_rfkill(TPACPI_RFK_UWB_SW_ID,
&uwb_tprfk_ops,
str_supported(video_supported != TPACPI_VIDEO_NONE),
video_supported);
- return (video_supported != TPACPI_VIDEO_NONE) ? 0 : 1;
+ return (video_supported != TPACPI_VIDEO_NONE) ? 0 : -ENODEV;
}
static void video_exit(void)
if (!kbdlight_is_supported()) {
tp_features.kbdlight = 0;
vdbg_printk(TPACPI_DBG_INIT, "kbdlight is unsupported\n");
- return 1;
+ return -ENODEV;
}
kbdlight_brightness = kbdlight_sysfs_get(NULL);
str_supported(tp_features.light_status));
if (!tp_features.light)
- return 1;
+ return -ENODEV;
rc = led_classdev_register(&tpacpi_pdev->dev,
&tpacpi_led_thinklight.led_classdev);
static DEVICE_ATTR_WO(cmos_command);
+static struct attribute *cmos_attributes[] = {
+ &dev_attr_cmos_command.attr,
+ NULL
+};
+
+static umode_t cmos_attr_is_visible(struct kobject *kobj,
+ struct attribute *attr, int n)
+{
+ return cmos_handle ? attr->mode : 0;
+}
+
+static const struct attribute_group cmos_attr_group = {
+ .is_visible = cmos_attr_is_visible,
+ .attrs = cmos_attributes,
+};
+
/* --------------------------------------------------------------------- */
static int __init cmos_init(struct ibm_init_struct *iibm)
{
- int res;
-
vdbg_printk(TPACPI_DBG_INIT,
- "initializing cmos commands subdriver\n");
+ "initializing cmos commands subdriver\n");
TPACPI_ACPIHANDLE_INIT(cmos);
vdbg_printk(TPACPI_DBG_INIT, "cmos commands are %s\n",
- str_supported(cmos_handle != NULL));
-
- res = device_create_file(&tpacpi_pdev->dev, &dev_attr_cmos_command);
- if (res)
- return res;
-
- return (cmos_handle) ? 0 : 1;
-}
+ str_supported(cmos_handle != NULL));
-static void cmos_exit(void)
-{
- device_remove_file(&tpacpi_pdev->dev, &dev_attr_cmos_command);
+ return cmos_handle ? 0 : -ENODEV;
}
static int cmos_read(struct seq_file *m)
.name = "cmos",
.read = cmos_read,
.write = cmos_write,
- .exit = cmos_exit,
};
/*************************************************************************
tpacpi_leds[led].led_classdev.brightness_get = &led_sysfs_get;
tpacpi_leds[led].led_classdev.name = tpacpi_led_names[led];
+ tpacpi_leds[led].led_classdev.flags = LED_RETAIN_AT_SHUTDOWN;
tpacpi_leds[led].led = led;
return led_classdev_register(&tpacpi_pdev->dev, &tpacpi_leds[led].led_classdev);
str_supported(led_supported), led_supported);
if (led_supported == TPACPI_LED_NONE)
- return 1;
+ return -ENODEV;
tpacpi_leds = kcalloc(TPACPI_LED_NUMLEDS, sizeof(*tpacpi_leds),
GFP_KERNEL);
tp_features.beep_needs_two_args = !!(quirks & TPACPI_BEEP_Q1);
- return (beep_handle) ? 0 : 1;
+ return (beep_handle) ? 0 : -ENODEV;
}
static int beep_read(struct seq_file *m)
};
static enum thermal_access_mode thermal_read_mode;
-static const struct attribute_group *thermal_attr_group;
static bool thermal_use_labels;
/* idx is zero-based */
&sensor_dev_attr_thermal_temp_input[X].dev_attr.attr
static struct attribute *thermal_temp_input_attr[] = {
- THERMAL_ATTRS(8),
- THERMAL_ATTRS(9),
- THERMAL_ATTRS(10),
- THERMAL_ATTRS(11),
- THERMAL_ATTRS(12),
- THERMAL_ATTRS(13),
- THERMAL_ATTRS(14),
- THERMAL_ATTRS(15),
THERMAL_ATTRS(0),
THERMAL_ATTRS(1),
THERMAL_ATTRS(2),
THERMAL_ATTRS(5),
THERMAL_ATTRS(6),
THERMAL_ATTRS(7),
+ THERMAL_ATTRS(8),
+ THERMAL_ATTRS(9),
+ THERMAL_ATTRS(10),
+ THERMAL_ATTRS(11),
+ THERMAL_ATTRS(12),
+ THERMAL_ATTRS(13),
+ THERMAL_ATTRS(14),
+ THERMAL_ATTRS(15),
NULL
};
-static const struct attribute_group thermal_temp_input16_group = {
- .attrs = thermal_temp_input_attr
-};
+static umode_t thermal_attr_is_visible(struct kobject *kobj,
+ struct attribute *attr, int n)
+{
+ if (thermal_read_mode == TPACPI_THERMAL_NONE)
+ return 0;
+
+ if (attr == THERMAL_ATTRS(8) || attr == THERMAL_ATTRS(9) ||
+ attr == THERMAL_ATTRS(10) || attr == THERMAL_ATTRS(11) ||
+ attr == THERMAL_ATTRS(12) || attr == THERMAL_ATTRS(13) ||
+ attr == THERMAL_ATTRS(14) || attr == THERMAL_ATTRS(15)) {
+ if (thermal_read_mode != TPACPI_THERMAL_TPEC_16)
+ return 0;
+ }
+
+ return attr->mode;
+}
-static const struct attribute_group thermal_temp_input8_group = {
- .attrs = &thermal_temp_input_attr[8]
+static const struct attribute_group thermal_attr_group = {
+ .is_visible = thermal_attr_is_visible,
+ .attrs = thermal_temp_input_attr,
};
#undef THERMAL_SENSOR_ATTR_TEMP
NULL
};
+static umode_t temp_label_attr_is_visible(struct kobject *kobj,
+ struct attribute *attr, int n)
+{
+ return thermal_use_labels ? attr->mode : 0;
+}
+
static const struct attribute_group temp_label_attr_group = {
+ .is_visible = temp_label_attr_is_visible,
.attrs = temp_label_attributes,
};
u8 t, ta1, ta2, ver = 0;
int i;
int acpi_tmp7;
- int res;
vdbg_printk(TPACPI_DBG_INIT, "initializing thermal subdriver\n");
str_supported(thermal_read_mode != TPACPI_THERMAL_NONE),
thermal_read_mode);
- switch (thermal_read_mode) {
- case TPACPI_THERMAL_TPEC_16:
- thermal_attr_group = &thermal_temp_input16_group;
- break;
- case TPACPI_THERMAL_TPEC_8:
- case TPACPI_THERMAL_ACPI_TMP07:
- case TPACPI_THERMAL_ACPI_UPDT:
- thermal_attr_group = &thermal_temp_input8_group;
- break;
- case TPACPI_THERMAL_NONE:
- default:
- return 1;
- }
-
- res = sysfs_create_group(&tpacpi_hwmon->kobj, thermal_attr_group);
- if (res)
- return res;
-
- if (thermal_use_labels) {
- res = sysfs_create_group(&tpacpi_hwmon->kobj, &temp_label_attr_group);
- if (res) {
- sysfs_remove_group(&tpacpi_hwmon->kobj, thermal_attr_group);
- return res;
- }
- }
-
- return 0;
-}
-
-static void thermal_exit(void)
-{
- if (thermal_attr_group)
- sysfs_remove_group(&tpacpi_hwmon->kobj, thermal_attr_group);
-
- if (thermal_use_labels)
- sysfs_remove_group(&tpacpi_hwmon->kobj, &temp_label_attr_group);
+ return thermal_read_mode != TPACPI_THERMAL_NONE ? 0 : -ENODEV;
}
static int thermal_read(struct seq_file *m)
static struct ibm_struct thermal_driver_data = {
.name = "thermal",
.read = thermal_read,
- .exit = thermal_exit,
};
/*************************************************************************
/* if it is unknown, we don't handle it: it wouldn't be safe */
if (tp_features.bright_unkfw)
- return 1;
+ return -ENODEV;
if (!brightness_enable) {
dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_BRGHT,
"brightness support disabled by module parameter\n");
- return 1;
+ return -ENODEV;
}
if (acpi_video_get_backlight_type() != acpi_backlight_vendor) {
if (brightness_enable > 1) {
pr_info("Standard ACPI backlight interface available, not loading native one\n");
- return 1;
+ return -ENODEV;
} else if (brightness_enable == 1) {
pr_warn("Cannot enable backlight brightness support, ACPI is already handling it. Refer to the acpi_backlight kernel parameter.\n");
- return 1;
+ return -ENODEV;
}
} else if (!tp_features.bright_acpimode) {
pr_notice("ACPI backlight interface not available\n");
- return 1;
+ return -ENODEV;
}
pr_notice("ACPI native brightness control enabled\n");
return -EINVAL;
if (tpacpi_brightness_get_raw(&b) < 0)
- return 1;
+ return -ENODEV;
memset(&props, 0, sizeof(struct backlight_properties));
props.type = BACKLIGHT_PLATFORM;
sizeof(struct tpacpi_alsa_data), &card);
if (rc < 0 || !card) {
pr_err("Failed to create ALSA card structures: %d\n", rc);
- return 1;
+ return -ENODEV;
}
BUG_ON(!card->private_data);
err_exit:
snd_card_free(card);
- return 1;
+ return -ENODEV;
}
#define TPACPI_VOL_Q_MUTEONLY 0x0001 /* Mute-only control available */
if (volume_mode == TPACPI_VOL_MODE_UCMS_STEP) {
pr_err("UCMS step volume mode not implemented, please contact %s\n",
TPACPI_MAIL);
- return 1;
+ return -ENODEV;
}
if (volume_capabilities >= TPACPI_VOL_CAP_MAX)
if (!alsa_enable) {
vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER,
"ALSA mixer disabled by parameter, not loading volume subdriver...\n");
- return 1;
+ return -ENODEV;
}
quirks = tpacpi_check_quirks(volume_quirk_table,
else if (quirks & TPACPI_VOL_Q_LEVEL)
tp_features.mixer_no_level_control = 0;
else
- return 1; /* no mixer */
+ return -ENODEV; /* no mixer */
break;
case TPACPI_VOL_CAP_VOLMUTE:
tp_features.mixer_no_level_control = 0;
tp_features.mixer_no_level_control = 1;
break;
default:
- return 1;
+ return -ENODEV;
}
if (volume_capabilities != TPACPI_VOL_CAP_AUTO)
{
pr_info("volume: disabled as there is no ALSA support in this kernel\n");
- return 1;
+ return -ENODEV;
}
static struct ibm_struct volume_driver_data = {
static DRIVER_ATTR_RW(fan_watchdog);
/* --------------------------------------------------------------------- */
+
static struct attribute *fan_attributes[] = {
- &dev_attr_pwm1_enable.attr, &dev_attr_pwm1.attr,
+ &dev_attr_pwm1_enable.attr,
+ &dev_attr_pwm1.attr,
&dev_attr_fan1_input.attr,
- NULL, /* for fan2_input */
+ &dev_attr_fan2_input.attr,
NULL
};
+static umode_t fan_attr_is_visible(struct kobject *kobj, struct attribute *attr,
+ int n)
+{
+ if (fan_status_access_mode == TPACPI_FAN_NONE &&
+ fan_control_access_mode == TPACPI_FAN_WR_NONE)
+ return 0;
+
+ if (attr == &dev_attr_fan2_input.attr) {
+ if (!tp_features.second_fan)
+ return 0;
+ }
+
+ return attr->mode;
+}
+
static const struct attribute_group fan_attr_group = {
+ .is_visible = fan_attr_is_visible,
.attrs = fan_attributes,
};
+static struct attribute *fan_driver_attributes[] = {
+ &driver_attr_fan_watchdog.attr,
+ NULL
+};
+
+static const struct attribute_group fan_driver_attr_group = {
+ .is_visible = fan_attr_is_visible,
+ .attrs = fan_driver_attributes,
+};
+
#define TPACPI_FAN_Q1 0x0001 /* Unitialized HFSP */
#define TPACPI_FAN_2FAN 0x0002 /* EC 0x31 bit 0 selects fan2 */
#define TPACPI_FAN_2CTL 0x0004 /* selects fan2 control */
static int __init fan_init(struct ibm_init_struct *iibm)
{
- int rc;
unsigned long quirks;
vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_FAN,
}
} else {
pr_err("ThinkPad ACPI EC access misbehaving, fan status and control unavailable\n");
- return 1;
+ return -ENODEV;
}
}
if (fan_status_access_mode != TPACPI_FAN_NONE)
fan_get_status_safe(NULL);
- if (fan_status_access_mode != TPACPI_FAN_NONE ||
- fan_control_access_mode != TPACPI_FAN_WR_NONE) {
- if (tp_features.second_fan) {
- /* attach second fan tachometer */
- fan_attributes[ARRAY_SIZE(fan_attributes)-2] =
- &dev_attr_fan2_input.attr;
- }
- rc = sysfs_create_group(&tpacpi_hwmon->kobj,
- &fan_attr_group);
- if (rc < 0)
- return rc;
+ if (fan_status_access_mode == TPACPI_FAN_NONE &&
+ fan_control_access_mode == TPACPI_FAN_WR_NONE)
+ return -ENODEV;
- rc = driver_create_file(&tpacpi_hwmon_pdriver.driver,
- &driver_attr_fan_watchdog);
- if (rc < 0) {
- sysfs_remove_group(&tpacpi_hwmon->kobj,
- &fan_attr_group);
- return rc;
- }
- return 0;
- } else
- return 1;
+ return 0;
}
static void fan_exit(void)
vdbg_printk(TPACPI_DBG_EXIT | TPACPI_DBG_FAN,
"cancelling any pending fan watchdog tasks\n");
- /* FIXME: can we really do this unconditionally? */
- sysfs_remove_group(&tpacpi_hwmon->kobj, &fan_attr_group);
- driver_remove_file(&tpacpi_hwmon_pdriver.driver,
- &driver_attr_fan_watchdog);
-
cancel_delayed_work(&fan_watchdog_task);
flush_workqueue(tpacpi_wq);
}
#define SET_START "BCCS"
#define GET_STOP "BCSG"
#define SET_STOP "BCSS"
+#define GET_DISCHARGE "BDSG"
+#define SET_DISCHARGE "BDSS"
+#define GET_INHIBIT "BICG"
+#define SET_INHIBIT "BICS"
enum {
BAT_ANY = 0,
/* This is used in the get/set helpers */
THRESHOLD_START,
THRESHOLD_STOP,
+ FORCE_DISCHARGE,
+ INHIBIT_CHARGE,
};
struct tpacpi_battery_data {
int start_support;
int charge_stop;
int stop_support;
+ unsigned int charge_behaviours;
};
struct tpacpi_battery_driver_data {
if (*ret == 0)
*ret = 100;
return 0;
+ case FORCE_DISCHARGE:
+ if ACPI_FAILURE(tpacpi_battery_acpi_eval(GET_DISCHARGE, ret, battery))
+ return -ENODEV;
+ /* The force discharge status is in bit 0 */
+ *ret = *ret & 0x01;
+ return 0;
+ case INHIBIT_CHARGE:
+ if ACPI_FAILURE(tpacpi_battery_acpi_eval(GET_INHIBIT, ret, battery))
+ return -ENODEV;
+ /* The inhibit charge status is in bit 0 */
+ *ret = *ret & 0x01;
+ return 0;
default:
pr_crit("wrong parameter: %d", what);
return -EINVAL;
return -ENODEV;
}
return 0;
+ case FORCE_DISCHARGE:
+ /* Force discharge is in bit 0,
+ * break on AC attach is in bit 1 (won't work on some ThinkPads),
+ * battery ID is in bits 8-9, 2 bits.
+ */
+ if (ACPI_FAILURE(tpacpi_battery_acpi_eval(SET_DISCHARGE, &ret, param))) {
+ pr_err("failed to set force discharge on %d", battery);
+ return -ENODEV;
+ }
+ return 0;
+ case INHIBIT_CHARGE:
+ /* When setting inhibit charge, we set a default value of
+ * always breaking on AC detach and the effective time is set to
+ * be permanent.
+ * The battery ID is in bits 4-5, 2 bits,
+ * the effective time is in bits 8-23, 2 bytes.
+ * A time of FFFF indicates forever.
+ */
+ param = value;
+ param |= battery << 4;
+ param |= 0xFFFF << 8;
+ if (ACPI_FAILURE(tpacpi_battery_acpi_eval(SET_INHIBIT, &ret, param))) {
+ pr_err("failed to set inhibit charge on %d", battery);
+ return -ENODEV;
+ }
+ return 0;
default:
pr_crit("wrong parameter: %d", what);
return -EINVAL;
}
}
+static int tpacpi_battery_set_validate(int what, int battery, int value)
+{
+ int ret, v;
+
+ ret = tpacpi_battery_set(what, battery, value);
+ if (ret < 0)
+ return ret;
+
+ ret = tpacpi_battery_get(what, battery, &v);
+ if (ret < 0)
+ return ret;
+
+ if (v == value)
+ return 0;
+
+ msleep(500);
+
+ ret = tpacpi_battery_get(what, battery, &v);
+ if (ret < 0)
+ return ret;
+
+ if (v == value)
+ return 0;
+
+ return -EIO;
+}
+
static int tpacpi_battery_probe(int battery)
{
int ret = 0;
* 2) Check for support
* 3) Get the current stop threshold
* 4) Check for support
+ * 5) Get the current force discharge status
+ * 6) Check for support
+ * 7) Get the current inhibit charge status
+ * 8) Check for support
*/
if (acpi_has_method(hkey_handle, GET_START)) {
if ACPI_FAILURE(tpacpi_battery_acpi_eval(GET_START, &ret, battery)) {
return -ENODEV;
}
}
- pr_info("battery %d registered (start %d, stop %d)",
- battery,
- battery_info.batteries[battery].charge_start,
- battery_info.batteries[battery].charge_stop);
+ if (acpi_has_method(hkey_handle, GET_DISCHARGE)) {
+ if (ACPI_FAILURE(tpacpi_battery_acpi_eval(GET_DISCHARGE, &ret, battery))) {
+ pr_err("Error probing battery discharge; %d\n", battery);
+ return -ENODEV;
+ }
+ /* Support is marked in bit 8 */
+ if (ret & BIT(8))
+ battery_info.batteries[battery].charge_behaviours |=
+ BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE);
+ }
+ if (acpi_has_method(hkey_handle, GET_INHIBIT)) {
+ if (ACPI_FAILURE(tpacpi_battery_acpi_eval(GET_INHIBIT, &ret, battery))) {
+ pr_err("Error probing battery inhibit charge; %d\n", battery);
+ return -ENODEV;
+ }
+ /* Support is marked in bit 5 */
+ if (ret & BIT(5))
+ battery_info.batteries[battery].charge_behaviours |=
+ BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE);
+ }
+
+ battery_info.batteries[battery].charge_behaviours |=
+ BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO);
+
+ pr_info("battery %d registered (start %d, stop %d, behaviours: 0x%x)\n",
+ battery,
+ battery_info.batteries[battery].charge_start,
+ battery_info.batteries[battery].charge_stop,
+ battery_info.batteries[battery].charge_behaviours);
return 0;
}
return tpacpi_battery_show(THRESHOLD_STOP, device, buf);
}
-static ssize_t charge_control_start_threshold_store(struct device *dev,
- struct device_attribute *attr,
- const char *buf, size_t count)
+static ssize_t charge_behaviour_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
{
- return tpacpi_battery_store(THRESHOLD_START, dev, buf, count);
-}
+ enum power_supply_charge_behaviour active = POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO;
+ struct power_supply *supply = to_power_supply(dev);
+ unsigned int available;
+ int ret, battery;
+
+ battery = tpacpi_battery_get_id(supply->desc->name);
+ available = battery_info.batteries[battery].charge_behaviours;
+
+ if (available & BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE)) {
+ if (tpacpi_battery_get(FORCE_DISCHARGE, battery, &ret))
+ return -ENODEV;
+ if (ret) {
+ active = POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE;
+ goto out;
+ }
+ }
+
+ if (available & BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE)) {
+ if (tpacpi_battery_get(INHIBIT_CHARGE, battery, &ret))
+ return -ENODEV;
+ if (ret) {
+ active = POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE;
+ goto out;
+ }
+ }
+
+out:
+ return power_supply_charge_behaviour_show(dev, available, active, buf);
+}
+
+static ssize_t charge_control_start_threshold_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ return tpacpi_battery_store(THRESHOLD_START, dev, buf, count);
+}
static ssize_t charge_control_end_threshold_store(struct device *dev,
struct device_attribute *attr,
return tpacpi_battery_store(THRESHOLD_STOP, dev, buf, count);
}
+static ssize_t charge_behaviour_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct power_supply *supply = to_power_supply(dev);
+ int selected, battery, ret = 0;
+ unsigned int available;
+
+ battery = tpacpi_battery_get_id(supply->desc->name);
+ available = battery_info.batteries[battery].charge_behaviours;
+ selected = power_supply_charge_behaviour_parse(available, buf);
+
+ if (selected < 0)
+ return selected;
+
+ switch (selected) {
+ case POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO:
+ if (available & BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE))
+ ret = tpacpi_battery_set_validate(FORCE_DISCHARGE, battery, 0);
+ if (available & BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE))
+ ret = min(ret, tpacpi_battery_set_validate(INHIBIT_CHARGE, battery, 0));
+ if (ret < 0)
+ return ret;
+ break;
+ case POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE:
+ if (available & BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE))
+ ret = tpacpi_battery_set_validate(INHIBIT_CHARGE, battery, 0);
+ ret = min(ret, tpacpi_battery_set_validate(FORCE_DISCHARGE, battery, 1));
+ if (ret < 0)
+ return ret;
+ break;
+ case POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE:
+ if (available & BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE))
+ ret = tpacpi_battery_set_validate(FORCE_DISCHARGE, battery, 0);
+ ret = min(ret, tpacpi_battery_set_validate(INHIBIT_CHARGE, battery, 1));
+ if (ret < 0)
+ return ret;
+ break;
+ default:
+ dev_err(dev, "Unexpected charge behaviour: %d\n", selected);
+ return -EINVAL;
+ }
+
+ return count;
+}
+
static DEVICE_ATTR_RW(charge_control_start_threshold);
static DEVICE_ATTR_RW(charge_control_end_threshold);
+static DEVICE_ATTR_RW(charge_behaviour);
static struct device_attribute dev_attr_charge_start_threshold = __ATTR(
charge_start_threshold,
0644,
&dev_attr_charge_control_end_threshold.attr,
&dev_attr_charge_start_threshold.attr,
&dev_attr_charge_stop_threshold.attr,
+ &dev_attr_charge_behaviour.attr,
NULL,
};
return 0;
lcdshadow_dev = drm_privacy_screen_register(&tpacpi_pdev->dev,
- &lcdshadow_ops);
+ &lcdshadow_ops, NULL);
if (IS_ERR(lcdshadow_dev))
return PTR_ERR(lcdshadow_dev);
return 0;
}
-static int dytc_get_version(void)
-{
- int err, output;
-
- /* Check if we've been called before - and just return cached value */
- if (dytc_version)
- return dytc_version;
-
- /* Otherwise query DYTC and extract version information */
- err = dytc_command(DYTC_CMD_QUERY, &output);
- /*
- * If support isn't available (ENODEV) then don't return an error
- * and don't create the sysfs group
- */
- if (err == -ENODEV)
- return 0;
- /* For all other errors we can flag the failure */
- if (err)
- return err;
-
- /* Check DYTC is enabled and supports mode setting */
- if (output & BIT(DYTC_QUERY_ENABLE_BIT))
- dytc_version = (output >> DYTC_QUERY_REV_BIT) & 0xF;
-
- return 0;
-}
-
static int lapsensor_get(bool *present, bool *state)
{
int output, err;
}
static DEVICE_ATTR_RO(palmsensor);
+static struct attribute *proxsensor_attributes[] = {
+ &dev_attr_dytc_lapmode.attr,
+ &dev_attr_palmsensor.attr,
+ NULL
+};
+
+static umode_t proxsensor_attr_is_visible(struct kobject *kobj,
+ struct attribute *attr, int n)
+{
+ if (attr == &dev_attr_dytc_lapmode.attr) {
+ /*
+ * Platforms before DYTC version 5 claim to have a lap sensor,
+ * but it doesn't work, so we ignore them.
+ */
+ if (!has_lapsensor || dytc_version < 5)
+ return 0;
+ } else if (attr == &dev_attr_palmsensor.attr) {
+ if (!has_palmsensor)
+ return 0;
+ }
+
+ return attr->mode;
+}
+
+static const struct attribute_group proxsensor_attr_group = {
+ .is_visible = proxsensor_attr_is_visible,
+ .attrs = proxsensor_attributes,
+};
+
static int tpacpi_proxsensor_init(struct ibm_init_struct *iibm)
{
- int palm_err, lap_err, err;
+ int palm_err, lap_err;
palm_err = palmsensor_get(&has_palmsensor, &palm_state);
lap_err = lapsensor_get(&has_lapsensor, &lap_state);
- /*
- * If support isn't available (ENODEV) for both devices then quit, but
- * don't return an error.
- */
+ /* If support isn't available for both devices return -ENODEV */
if ((palm_err == -ENODEV) && (lap_err == -ENODEV))
- return 0;
+ return -ENODEV;
/* Otherwise, if there was an error return it */
if (palm_err && (palm_err != -ENODEV))
return palm_err;
if (lap_err && (lap_err != -ENODEV))
return lap_err;
- if (has_palmsensor) {
- err = sysfs_create_file(&tpacpi_pdev->dev.kobj, &dev_attr_palmsensor.attr);
- if (err)
- return err;
- }
-
- /* Check if we know the DYTC version, if we don't then get it */
- if (!dytc_version) {
- err = dytc_get_version();
- if (err)
- return err;
- }
- /*
- * Platforms before DYTC version 5 claim to have a lap sensor, but it doesn't work, so we
- * ignore them
- */
- if (has_lapsensor && (dytc_version >= 5)) {
- err = sysfs_create_file(&tpacpi_pdev->dev.kobj, &dev_attr_dytc_lapmode.attr);
- if (err)
- return err;
- }
return 0;
}
-static void proxsensor_exit(void)
-{
- if (has_lapsensor)
- sysfs_remove_file(&tpacpi_pdev->dev.kobj, &dev_attr_dytc_lapmode.attr);
- if (has_palmsensor)
- sysfs_remove_file(&tpacpi_pdev->dev.kobj, &dev_attr_palmsensor.attr);
-}
-
static struct ibm_struct proxsensor_driver_data = {
.name = "proximity-sensor",
- .exit = proxsensor_exit,
};
/*************************************************************************
#define DYTC_ENABLE_CQL DYTC_SET_COMMAND(DYTC_FUNCTION_CQL, DYTC_MODE_BALANCE, 1)
-static bool dytc_profile_available;
static enum platform_profile_option dytc_current_profile;
static atomic_t dytc_ignore_event = ATOMIC_INIT(0);
static DEFINE_MUTEX(dytc_mutex);
int output;
int err;
- if (!dytc_profile_available)
- return -ENODEV;
-
err = mutex_lock_interruptible(&dytc_mutex);
if (err)
return err;
set_bit(PLATFORM_PROFILE_BALANCED, dytc_profile.choices);
set_bit(PLATFORM_PROFILE_PERFORMANCE, dytc_profile.choices);
- dytc_profile_available = false;
err = dytc_command(DYTC_CMD_QUERY, &output);
- /*
- * If support isn't available (ENODEV) then don't return an error
- * and don't create the sysfs group
- */
- if (err == -ENODEV)
- return 0;
- /* For all other errors we can flag the failure */
if (err)
return err;
- /* Check if we know the DYTC version, if we don't then get it */
- if (!dytc_version) {
- err = dytc_get_version();
- if (err)
- return err;
- }
+ if (output & BIT(DYTC_QUERY_ENABLE_BIT))
+ dytc_version = (output >> DYTC_QUERY_REV_BIT) & 0xF;
+
/* Check DYTC is enabled and supports mode setting */
- if (dytc_version >= 5) {
- dbg_printk(TPACPI_DBG_INIT,
- "DYTC version %d: thermal mode available\n", dytc_version);
- /*
- * Check if MMC_GET functionality available
- * Version > 6 and return success from MMC_GET command
- */
- dytc_mmc_get_available = false;
- if (dytc_version >= 6) {
- err = dytc_command(DYTC_CMD_MMC_GET, &output);
- if (!err && ((output & DYTC_ERR_MASK) == DYTC_ERR_SUCCESS))
- dytc_mmc_get_available = true;
- }
- /* Create platform_profile structure and register */
- err = platform_profile_register(&dytc_profile);
- /*
- * If for some reason platform_profiles aren't enabled
- * don't quit terminally.
- */
- if (err)
- return 0;
+ if (dytc_version < 5)
+ return -ENODEV;
- dytc_profile_available = true;
- /* Ensure initial values are correct */
- dytc_profile_refresh();
+ dbg_printk(TPACPI_DBG_INIT,
+ "DYTC version %d: thermal mode available\n", dytc_version);
+ /*
+ * Check if MMC_GET functionality available
+ * Version > 6 and return success from MMC_GET command
+ */
+ dytc_mmc_get_available = false;
+ if (dytc_version >= 6) {
+ err = dytc_command(DYTC_CMD_MMC_GET, &output);
+ if (!err && ((output & DYTC_ERR_MASK) == DYTC_ERR_SUCCESS))
+ dytc_mmc_get_available = true;
}
+ /* Create platform_profile structure and register */
+ err = platform_profile_register(&dytc_profile);
+ /*
+ * If for some reason platform_profiles aren't enabled
+ * don't quit terminally.
+ */
+ if (err)
+ return -ENODEV;
+
+ /* Ensure initial values are correct */
+ dytc_profile_refresh();
+
return 0;
}
static void dytc_profile_exit(void)
{
- if (dytc_profile_available) {
- dytc_profile_available = false;
- platform_profile_remove();
- }
+ platform_profile_remove();
}
static struct ibm_struct dytc_profile_driver_data = {
NULL
};
+static umode_t kbdlang_attr_is_visible(struct kobject *kobj,
+ struct attribute *attr, int n)
+{
+ return tp_features.kbd_lang ? attr->mode : 0;
+}
+
static const struct attribute_group kbdlang_attr_group = {
+ .is_visible = kbdlang_attr_is_visible,
.attrs = kbdlang_attributes,
};
int err, output;
err = get_keyboard_lang(&output);
- /*
- * If support isn't available (ENODEV) then don't return an error
- * just don't create the sysfs group.
- */
- if (err == -ENODEV)
- return 0;
-
- if (err)
- return err;
-
- /* Platform supports this feature - create the sysfs file */
- return sysfs_create_group(&tpacpi_pdev->dev.kobj, &kbdlang_attr_group);
-}
-
-static void kbdlang_exit(void)
-{
- sysfs_remove_group(&tpacpi_pdev->dev.kobj, &kbdlang_attr_group);
+ tp_features.kbd_lang = !err;
+ return err;
}
static struct ibm_struct kbdlang_driver_data = {
.name = "kbdlang",
- .exit = kbdlang_exit,
};
/*************************************************************************
}
static DEVICE_ATTR_RO(wwan_antenna_type);
-static int tpacpi_dprc_init(struct ibm_init_struct *iibm)
+static struct attribute *dprc_attributes[] = {
+ &dev_attr_wwan_antenna_type.attr,
+ NULL
+};
+
+static umode_t dprc_attr_is_visible(struct kobject *kobj,
+ struct attribute *attr, int n)
{
- int wwanantenna_err, err;
+ return has_antennatype ? attr->mode : 0;
+}
- wwanantenna_err = get_wwan_antenna(&wwan_antennatype);
- /*
- * If support isn't available (ENODEV) then quit, but don't
- * return an error.
- */
- if (wwanantenna_err == -ENODEV)
- return 0;
+static const struct attribute_group dprc_attr_group = {
+ .is_visible = dprc_attr_is_visible,
+ .attrs = dprc_attributes,
+};
- /* if there was an error return it */
- if (wwanantenna_err && (wwanantenna_err != -ENODEV))
- return wwanantenna_err;
- else if (!wwanantenna_err)
- has_antennatype = true;
+static int tpacpi_dprc_init(struct ibm_init_struct *iibm)
+{
+ int err;
- if (has_antennatype) {
- err = sysfs_create_file(&tpacpi_pdev->dev.kobj, &dev_attr_wwan_antenna_type.attr);
- if (err)
- return err;
- }
+ err = get_wwan_antenna(&wwan_antennatype);
+ if (err)
+ return err;
+
+ has_antennatype = true;
return 0;
}
-static void dprc_exit(void)
+static struct ibm_struct dprc_driver_data = {
+ .name = "dprc",
+};
+
+/* --------------------------------------------------------------------- */
+
+static struct attribute *tpacpi_driver_attributes[] = {
+ &driver_attr_debug_level.attr,
+ &driver_attr_version.attr,
+ &driver_attr_interface_version.attr,
+#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
+ &driver_attr_wlsw_emulstate.attr,
+ &driver_attr_bluetooth_emulstate.attr,
+ &driver_attr_wwan_emulstate.attr,
+ &driver_attr_uwb_emulstate.attr,
+#endif
+ NULL
+};
+
+#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
+static umode_t tpacpi_attr_is_visible(struct kobject *kobj,
+ struct attribute *attr, int n)
{
- if (has_antennatype)
- sysfs_remove_file(&tpacpi_pdev->dev.kobj, &dev_attr_wwan_antenna_type.attr);
+ if (attr == &driver_attr_wlsw_emulstate.attr) {
+ if (!dbg_wlswemul)
+ return 0;
+ } else if (attr == &driver_attr_bluetooth_emulstate.attr) {
+ if (!dbg_bluetoothemul)
+ return 0;
+ } else if (attr == &driver_attr_wwan_emulstate.attr) {
+ if (!dbg_wwanemul)
+ return 0;
+ } else if (attr == &driver_attr_uwb_emulstate.attr) {
+ if (!dbg_uwbemul)
+ return 0;
+ }
+
+ return attr->mode;
}
+#endif
-static struct ibm_struct dprc_driver_data = {
- .name = "dprc",
- .exit = dprc_exit,
+static const struct attribute_group tpacpi_driver_attr_group = {
+#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
+ .is_visible = tpacpi_attr_is_visible,
+#endif
+ .attrs = tpacpi_driver_attributes,
+};
+
+static const struct attribute_group *tpacpi_driver_groups[] = {
+ &tpacpi_driver_attr_group,
+ NULL,
+};
+
+static const struct attribute_group *tpacpi_groups[] = {
+ &adaptive_kbd_attr_group,
+ &hotkey_attr_group,
+ &bluetooth_attr_group,
+ &wan_attr_group,
+ &cmos_attr_group,
+ &proxsensor_attr_group,
+ &kbdlang_attr_group,
+ &dprc_attr_group,
+ NULL,
+};
+
+static const struct attribute_group *tpacpi_hwmon_groups[] = {
+ &thermal_attr_group,
+ &temp_label_attr_group,
+ &fan_attr_group,
+ NULL,
+};
+
+static const struct attribute_group *tpacpi_hwmon_driver_groups[] = {
+ &fan_driver_attr_group,
+ NULL,
+};
+
+/****************************************************************************
+ ****************************************************************************
+ *
+ * Platform drivers
+ *
+ ****************************************************************************
+ ****************************************************************************/
+
+static struct platform_driver tpacpi_pdriver = {
+ .driver = {
+ .name = TPACPI_DRVR_NAME,
+ .pm = &tpacpi_pm,
+ .groups = tpacpi_driver_groups,
+ .dev_groups = tpacpi_groups,
+ },
+ .shutdown = tpacpi_shutdown_handler,
+};
+
+static struct platform_driver tpacpi_hwmon_pdriver = {
+ .driver = {
+ .name = TPACPI_HWMON_DRVR_NAME,
+ .groups = tpacpi_hwmon_driver_groups,
+ },
};
/****************************************************************************
if (iibm->init) {
ret = iibm->init(iibm);
- if (ret > 0)
- return 0; /* probe failed */
+ if (ret > 0 || ret == -ENODEV)
+ return 0; /* subdriver functionality not available */
if (ret)
return ret;
for (i = 0; i < ARRAY_SIZE(ibms_init); i++) {
ibm = ibms_init[i].data;
- WARN_ON(ibm == NULL);
-
if (!ibm || !ibm->name)
continue;
tpacpi_lifecycle = TPACPI_LIFE_EXITING;
+ if (tpacpi_hwmon)
+ hwmon_device_unregister(tpacpi_hwmon);
+ if (tp_features.sensors_pdrv_registered)
+ platform_driver_unregister(&tpacpi_hwmon_pdriver);
+ if (tp_features.platform_drv_registered)
+ platform_driver_unregister(&tpacpi_pdriver);
+
list_for_each_entry_safe_reverse(ibm, itmp,
&tpacpi_all_drivers,
all_drivers) {
kfree(hotkey_keycode_map);
}
- if (tpacpi_hwmon)
- hwmon_device_unregister(tpacpi_hwmon);
-
if (tpacpi_sensors_pdev)
platform_device_unregister(tpacpi_sensors_pdev);
if (tpacpi_pdev)
platform_device_unregister(tpacpi_pdev);
-
- if (tp_features.sensors_pdrv_attrs_registered)
- tpacpi_remove_driver_attributes(&tpacpi_hwmon_pdriver.driver);
- if (tp_features.platform_drv_attrs_registered)
- tpacpi_remove_driver_attributes(&tpacpi_pdriver.driver);
-
- if (tp_features.sensors_pdrv_registered)
- platform_driver_unregister(&tpacpi_hwmon_pdriver);
-
- if (tp_features.platform_drv_registered)
- platform_driver_unregister(&tpacpi_pdriver);
-
if (proc_dir)
remove_proc_entry(TPACPI_PROC_DIR, acpi_root_dir);
-
if (tpacpi_wq)
destroy_workqueue(tpacpi_wq);
return -ENODEV;
}
- ret = platform_driver_register(&tpacpi_pdriver);
- if (ret) {
- pr_err("unable to register main platform driver\n");
- thinkpad_acpi_module_exit();
- return ret;
- }
- tp_features.platform_drv_registered = 1;
-
- ret = platform_driver_register(&tpacpi_hwmon_pdriver);
- if (ret) {
- pr_err("unable to register hwmon platform driver\n");
- thinkpad_acpi_module_exit();
- return ret;
- }
- tp_features.sensors_pdrv_registered = 1;
-
- ret = tpacpi_create_driver_attributes(&tpacpi_pdriver.driver);
- if (!ret) {
- tp_features.platform_drv_attrs_registered = 1;
- ret = tpacpi_create_driver_attributes(
- &tpacpi_hwmon_pdriver.driver);
- }
- if (ret) {
- pr_err("unable to create sysfs driver attributes\n");
- thinkpad_acpi_module_exit();
- return ret;
- }
- tp_features.sensors_pdrv_attrs_registered = 1;
-
-
/* Device initialization */
tpacpi_pdev = platform_device_register_simple(TPACPI_DRVR_NAME, -1,
NULL, 0);
thinkpad_acpi_module_exit();
return ret;
}
- tp_features.sensors_pdev_attrs_registered = 1;
- tpacpi_hwmon = hwmon_device_register_with_groups(
- &tpacpi_sensors_pdev->dev, TPACPI_NAME, NULL, NULL);
- if (IS_ERR(tpacpi_hwmon)) {
- ret = PTR_ERR(tpacpi_hwmon);
- tpacpi_hwmon = NULL;
- pr_err("unable to register hwmon device\n");
- thinkpad_acpi_module_exit();
- return ret;
- }
mutex_init(&tpacpi_inputdev_send_mutex);
tpacpi_inputdev = input_allocate_device();
if (!tpacpi_inputdev) {
tpacpi_lifecycle = TPACPI_LIFE_RUNNING;
+ ret = platform_driver_register(&tpacpi_pdriver);
+ if (ret) {
+ pr_err("unable to register main platform driver\n");
+ thinkpad_acpi_module_exit();
+ return ret;
+ }
+ tp_features.platform_drv_registered = 1;
+
+ ret = platform_driver_register(&tpacpi_hwmon_pdriver);
+ if (ret) {
+ pr_err("unable to register hwmon platform driver\n");
+ thinkpad_acpi_module_exit();
+ return ret;
+ }
+ tp_features.sensors_pdrv_registered = 1;
+
+ tpacpi_hwmon = hwmon_device_register_with_groups(
+ &tpacpi_sensors_pdev->dev, TPACPI_NAME, NULL, tpacpi_hwmon_groups);
+ if (IS_ERR(tpacpi_hwmon)) {
+ ret = PTR_ERR(tpacpi_hwmon);
+ tpacpi_hwmon = NULL;
+ pr_err("unable to register hwmon device\n");
+ thinkpad_acpi_module_exit();
+ return ret;
+ }
+
ret = input_register_device(tpacpi_inputdev);
if (ret < 0) {
pr_err("unable to register input device\n");
#include <linux/init.h>
#include <linux/linux_logo.h>
#include <linux/proc_fs.h>
+ #include <linux/platform_device.h>
#include <linux/seq_file.h>
#include <linux/console.h>
#include <linux/kmod.h>
/* check all firmware fbs and kick off if the base addr overlaps */
for_each_registered_fb(i) {
struct apertures_struct *gen_aper;
+ struct device *device;
if (!(registered_fb[i]->flags & FBINFO_MISC_FIRMWARE))
continue;
gen_aper = registered_fb[i]->apertures;
+ device = registered_fb[i]->device;
if (fb_do_apertures_overlap(gen_aper, a) ||
(primary && gen_aper && gen_aper->count &&
gen_aper->ranges[0].base == VGA_FB_PHYS)) {
printk(KERN_INFO "fb%d: switching to %s from %s\n",
i, name, registered_fb[i]->fix.id);
- do_unregister_framebuffer(registered_fb[i]);
+
+ /*
+ * If we kick-out a firmware driver, we also want to remove
+ * the underlying platform device, such as simple-framebuffer,
+ * VESA, EFI, etc. A native driver will then be able to
+ * allocate the memory range.
+ *
+ * If it's not a platform device, at least print a warning. A
+ * fix would add code to remove the device from the system.
+ */
+ if (dev_is_platform(device)) {
+ registered_fb[i]->forced_out = true;
+ platform_device_unregister(to_platform_device(device));
+ } else {
+ pr_warn("fb%d: cannot remove device\n", i);
+ do_unregister_framebuffer(registered_fb[i]);
+ }
}
}
}
}
EXPORT_SYMBOL(remove_conflicting_framebuffers);
+/**
+ * is_firmware_framebuffer - detect if firmware-configured framebuffer matches
+ * @a: memory range, users of which are to be checked
+ *
+ * This function checks framebuffer devices (initialized by firmware/bootloader)
+ * which use memory range described by @a. If @a matchesm the function returns
+ * true, otherwise false.
+ */
+bool is_firmware_framebuffer(struct apertures_struct *a)
+{
+ bool do_free = false;
+ bool found = false;
+ int i;
+
+ if (!a) {
+ a = alloc_apertures(1);
+ if (!a)
+ return false;
+
+ a->ranges[0].base = 0;
+ a->ranges[0].size = ~0;
+ do_free = true;
+ }
+
+ mutex_lock(®istration_lock);
+ /* check all firmware fbs and kick off if the base addr overlaps */
+ for_each_registered_fb(i) {
+ struct apertures_struct *gen_aper;
+
+ if (!(registered_fb[i]->flags & FBINFO_MISC_FIRMWARE))
+ continue;
+
+ gen_aper = registered_fb[i]->apertures;
+ if (fb_do_apertures_overlap(gen_aper, a)) {
+ found = true;
+ break;
+ }
+ }
+ mutex_unlock(®istration_lock);
+
+ if (do_free)
+ kfree(a);
+
+ return found;
+}
+EXPORT_SYMBOL(is_firmware_framebuffer);
+
/**
* remove_conflicting_pci_framebuffers - remove firmware-configured framebuffers for PCI devices
* @pdev: PCI device
void
unregister_framebuffer(struct fb_info *fb_info)
{
- mutex_lock(®istration_lock);
+ bool forced_out = fb_info->forced_out;
+
+ if (!forced_out)
+ mutex_lock(®istration_lock);
do_unregister_framebuffer(fb_info);
- mutex_unlock(®istration_lock);
+ if (!forced_out)
+ mutex_unlock(®istration_lock);
}
EXPORT_SYMBOL(unregister_framebuffer);
vga_io_w(VGA_GFX_I, index);
}
+/* Check if the video mode is supported by the driver */
+static inline int check_mode_supported(void)
+{
+ /* non-x86 architectures treat orig_video_isVGA as a boolean flag */
+#if defined(CONFIG_X86)
+ /* only EGA and VGA in 16 color graphic mode are supported */
+ if (screen_info.orig_video_isVGA != VIDEO_TYPE_EGAC &&
+ screen_info.orig_video_isVGA != VIDEO_TYPE_VGAC)
+ return -ENODEV;
+
+ if (screen_info.orig_video_mode != 0x0D && /* 320x200/4 (EGA) */
+ screen_info.orig_video_mode != 0x0E && /* 640x200/4 (EGA) */
+ screen_info.orig_video_mode != 0x10 && /* 640x350/4 (EGA) */
+ screen_info.orig_video_mode != 0x12) /* 640x480/4 (VGA) */
+ return -ENODEV;
+#endif
+ return 0;
+}
+
static void vga16fb_pan_var(struct fb_info *info,
struct fb_var_screeninfo *var)
{
printk(KERN_INFO "vga16fb: mapped to 0x%p\n", info->screen_base);
par = info->par;
+ #if defined(CONFIG_X86)
+ par->isVGA = screen_info.orig_video_isVGA == VIDEO_TYPE_VGAC;
+ #else
+ /* non-x86 architectures treat orig_video_isVGA as a boolean flag */
par->isVGA = screen_info.orig_video_isVGA;
+ #endif
par->palette_blanked = 0;
par->vesa_blanked = 0;
vga16fb_setup(option);
#endif
+
+ ret = check_mode_supported();
+ if (ret)
+ return ret;
+
ret = platform_driver_register(&vga16fb_driver);
if (!ret) {
--- /dev/null
+ /*
+ * Copyright © 2008 Keith Packard
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting documentation, and
+ * that the name of the copyright holders not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission. The copyright holders make no representations
+ * about the suitability of this software for any purpose. It is provided "as
+ * is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+ * OF THIS SOFTWARE.
+ */
+
+ #ifndef _DRM_DP_HELPER_H_
+ #define _DRM_DP_HELPER_H_
+
+ #include <linux/delay.h>
+ #include <linux/i2c.h>
+ #include <linux/types.h>
+ #include <drm/drm_connector.h>
+
+ struct drm_device;
+ struct drm_dp_aux;
+ struct drm_panel;
+
+ /*
+ * Unless otherwise noted, all values are from the DP 1.1a spec. Note that
+ * DP and DPCD versions are independent. Differences from 1.0 are not noted,
+ * 1.0 devices basically don't exist in the wild.
+ *
+ * Abbreviations, in chronological order:
+ *
+ * eDP: Embedded DisplayPort version 1
+ * DPI: DisplayPort Interoperability Guideline v1.1a
+ * 1.2: DisplayPort 1.2
+ * MST: Multistream Transport - part of DP 1.2a
+ *
+ * 1.2 formally includes both eDP and DPI definitions.
+ */
+
+ /* MSA (Main Stream Attribute) MISC bits (as MISC1<<8|MISC0) */
+ #define DP_MSA_MISC_SYNC_CLOCK (1 << 0)
+ #define DP_MSA_MISC_INTERLACE_VTOTAL_EVEN (1 << 8)
+ #define DP_MSA_MISC_STEREO_NO_3D (0 << 9)
+ #define DP_MSA_MISC_STEREO_PROG_RIGHT_EYE (1 << 9)
+ #define DP_MSA_MISC_STEREO_PROG_LEFT_EYE (3 << 9)
+ /* bits per component for non-RAW */
+ #define DP_MSA_MISC_6_BPC (0 << 5)
+ #define DP_MSA_MISC_8_BPC (1 << 5)
+ #define DP_MSA_MISC_10_BPC (2 << 5)
+ #define DP_MSA_MISC_12_BPC (3 << 5)
+ #define DP_MSA_MISC_16_BPC (4 << 5)
+ /* bits per component for RAW */
+ #define DP_MSA_MISC_RAW_6_BPC (1 << 5)
+ #define DP_MSA_MISC_RAW_7_BPC (2 << 5)
+ #define DP_MSA_MISC_RAW_8_BPC (3 << 5)
+ #define DP_MSA_MISC_RAW_10_BPC (4 << 5)
+ #define DP_MSA_MISC_RAW_12_BPC (5 << 5)
+ #define DP_MSA_MISC_RAW_14_BPC (6 << 5)
+ #define DP_MSA_MISC_RAW_16_BPC (7 << 5)
+ /* pixel encoding/colorimetry format */
+ #define _DP_MSA_MISC_COLOR(misc1_7, misc0_21, misc0_3, misc0_4) \
+ ((misc1_7) << 15 | (misc0_4) << 4 | (misc0_3) << 3 | ((misc0_21) << 1))
+ #define DP_MSA_MISC_COLOR_RGB _DP_MSA_MISC_COLOR(0, 0, 0, 0)
+ #define DP_MSA_MISC_COLOR_CEA_RGB _DP_MSA_MISC_COLOR(0, 0, 1, 0)
+ #define DP_MSA_MISC_COLOR_RGB_WIDE_FIXED _DP_MSA_MISC_COLOR(0, 3, 0, 0)
+ #define DP_MSA_MISC_COLOR_RGB_WIDE_FLOAT _DP_MSA_MISC_COLOR(0, 3, 0, 1)
+ #define DP_MSA_MISC_COLOR_Y_ONLY _DP_MSA_MISC_COLOR(1, 0, 0, 0)
+ #define DP_MSA_MISC_COLOR_RAW _DP_MSA_MISC_COLOR(1, 1, 0, 0)
+ #define DP_MSA_MISC_COLOR_YCBCR_422_BT601 _DP_MSA_MISC_COLOR(0, 1, 1, 0)
+ #define DP_MSA_MISC_COLOR_YCBCR_422_BT709 _DP_MSA_MISC_COLOR(0, 1, 1, 1)
+ #define DP_MSA_MISC_COLOR_YCBCR_444_BT601 _DP_MSA_MISC_COLOR(0, 2, 1, 0)
+ #define DP_MSA_MISC_COLOR_YCBCR_444_BT709 _DP_MSA_MISC_COLOR(0, 2, 1, 1)
+ #define DP_MSA_MISC_COLOR_XVYCC_422_BT601 _DP_MSA_MISC_COLOR(0, 1, 0, 0)
+ #define DP_MSA_MISC_COLOR_XVYCC_422_BT709 _DP_MSA_MISC_COLOR(0, 1, 0, 1)
+ #define DP_MSA_MISC_COLOR_XVYCC_444_BT601 _DP_MSA_MISC_COLOR(0, 2, 0, 0)
+ #define DP_MSA_MISC_COLOR_XVYCC_444_BT709 _DP_MSA_MISC_COLOR(0, 2, 0, 1)
+ #define DP_MSA_MISC_COLOR_OPRGB _DP_MSA_MISC_COLOR(0, 0, 1, 1)
+ #define DP_MSA_MISC_COLOR_DCI_P3 _DP_MSA_MISC_COLOR(0, 3, 1, 0)
+ #define DP_MSA_MISC_COLOR_COLOR_PROFILE _DP_MSA_MISC_COLOR(0, 3, 1, 1)
+ #define DP_MSA_MISC_COLOR_VSC_SDP (1 << 14)
+
+ #define DP_AUX_MAX_PAYLOAD_BYTES 16
+
+ #define DP_AUX_I2C_WRITE 0x0
+ #define DP_AUX_I2C_READ 0x1
+ #define DP_AUX_I2C_WRITE_STATUS_UPDATE 0x2
+ #define DP_AUX_I2C_MOT 0x4
+ #define DP_AUX_NATIVE_WRITE 0x8
+ #define DP_AUX_NATIVE_READ 0x9
+
+ #define DP_AUX_NATIVE_REPLY_ACK (0x0 << 0)
+ #define DP_AUX_NATIVE_REPLY_NACK (0x1 << 0)
+ #define DP_AUX_NATIVE_REPLY_DEFER (0x2 << 0)
+ #define DP_AUX_NATIVE_REPLY_MASK (0x3 << 0)
+
+ #define DP_AUX_I2C_REPLY_ACK (0x0 << 2)
+ #define DP_AUX_I2C_REPLY_NACK (0x1 << 2)
+ #define DP_AUX_I2C_REPLY_DEFER (0x2 << 2)
+ #define DP_AUX_I2C_REPLY_MASK (0x3 << 2)
+
+ /* DPCD Field Address Mapping */
+
+ /* Receiver Capability */
+ #define DP_DPCD_REV 0x000
+ # define DP_DPCD_REV_10 0x10
+ # define DP_DPCD_REV_11 0x11
+ # define DP_DPCD_REV_12 0x12
+ # define DP_DPCD_REV_13 0x13
+ # define DP_DPCD_REV_14 0x14
+
+ #define DP_MAX_LINK_RATE 0x001
+
+ #define DP_MAX_LANE_COUNT 0x002
+ # define DP_MAX_LANE_COUNT_MASK 0x1f
+ # define DP_TPS3_SUPPORTED (1 << 6) /* 1.2 */
+ # define DP_ENHANCED_FRAME_CAP (1 << 7)
+
+ #define DP_MAX_DOWNSPREAD 0x003
+ # define DP_MAX_DOWNSPREAD_0_5 (1 << 0)
+ # define DP_STREAM_REGENERATION_STATUS_CAP (1 << 1) /* 2.0 */
+ # define DP_NO_AUX_HANDSHAKE_LINK_TRAINING (1 << 6)
+ # define DP_TPS4_SUPPORTED (1 << 7)
+
+ #define DP_NORP 0x004
+
+ #define DP_DOWNSTREAMPORT_PRESENT 0x005
+ # define DP_DWN_STRM_PORT_PRESENT (1 << 0)
+ # define DP_DWN_STRM_PORT_TYPE_MASK 0x06
+ # define DP_DWN_STRM_PORT_TYPE_DP (0 << 1)
+ # define DP_DWN_STRM_PORT_TYPE_ANALOG (1 << 1)
+ # define DP_DWN_STRM_PORT_TYPE_TMDS (2 << 1)
+ # define DP_DWN_STRM_PORT_TYPE_OTHER (3 << 1)
+ # define DP_FORMAT_CONVERSION (1 << 3)
+ # define DP_DETAILED_CAP_INFO_AVAILABLE (1 << 4) /* DPI */
+
+ #define DP_MAIN_LINK_CHANNEL_CODING 0x006
+ # define DP_CAP_ANSI_8B10B (1 << 0)
+ # define DP_CAP_ANSI_128B132B (1 << 1) /* 2.0 */
+
+ #define DP_DOWN_STREAM_PORT_COUNT 0x007
+ # define DP_PORT_COUNT_MASK 0x0f
+ # define DP_MSA_TIMING_PAR_IGNORED (1 << 6) /* eDP */
+ # define DP_OUI_SUPPORT (1 << 7)
+
+ #define DP_RECEIVE_PORT_0_CAP_0 0x008
+ # define DP_LOCAL_EDID_PRESENT (1 << 1)
+ # define DP_ASSOCIATED_TO_PRECEDING_PORT (1 << 2)
+
+ #define DP_RECEIVE_PORT_0_BUFFER_SIZE 0x009
+
+ #define DP_RECEIVE_PORT_1_CAP_0 0x00a
+ #define DP_RECEIVE_PORT_1_BUFFER_SIZE 0x00b
+
+ #define DP_I2C_SPEED_CAP 0x00c /* DPI */
+ # define DP_I2C_SPEED_1K 0x01
+ # define DP_I2C_SPEED_5K 0x02
+ # define DP_I2C_SPEED_10K 0x04
+ # define DP_I2C_SPEED_100K 0x08
+ # define DP_I2C_SPEED_400K 0x10
+ # define DP_I2C_SPEED_1M 0x20
+
+ #define DP_EDP_CONFIGURATION_CAP 0x00d /* XXX 1.2? */
+ # define DP_ALTERNATE_SCRAMBLER_RESET_CAP (1 << 0)
+ # define DP_FRAMING_CHANGE_CAP (1 << 1)
+ # define DP_DPCD_DISPLAY_CONTROL_CAPABLE (1 << 3) /* edp v1.2 or higher */
+
+ #define DP_TRAINING_AUX_RD_INTERVAL 0x00e /* XXX 1.2? */
+ # define DP_TRAINING_AUX_RD_MASK 0x7F /* DP 1.3 */
+ # define DP_EXTENDED_RECEIVER_CAP_FIELD_PRESENT (1 << 7) /* DP 1.3 */
+
+ #define DP_ADAPTER_CAP 0x00f /* 1.2 */
+ # define DP_FORCE_LOAD_SENSE_CAP (1 << 0)
+ # define DP_ALTERNATE_I2C_PATTERN_CAP (1 << 1)
+
+ #define DP_SUPPORTED_LINK_RATES 0x010 /* eDP 1.4 */
+ # define DP_MAX_SUPPORTED_RATES 8 /* 16-bit little-endian */
+
+ /* Multiple stream transport */
+ #define DP_FAUX_CAP 0x020 /* 1.2 */
+ # define DP_FAUX_CAP_1 (1 << 0)
+
+ #define DP_SINK_VIDEO_FALLBACK_FORMATS 0x020 /* 2.0 */
+ # define DP_FALLBACK_1024x768_60HZ_24BPP (1 << 0)
+ # define DP_FALLBACK_1280x720_60HZ_24BPP (1 << 1)
+ # define DP_FALLBACK_1920x1080_60HZ_24BPP (1 << 2)
+
+ #define DP_MSTM_CAP 0x021 /* 1.2 */
+ # define DP_MST_CAP (1 << 0)
+ # define DP_SINGLE_STREAM_SIDEBAND_MSG (1 << 1) /* 2.0 */
+
+ #define DP_NUMBER_OF_AUDIO_ENDPOINTS 0x022 /* 1.2 */
+
+ /* AV_SYNC_DATA_BLOCK 1.2 */
+ #define DP_AV_GRANULARITY 0x023
+ # define DP_AG_FACTOR_MASK (0xf << 0)
+ # define DP_AG_FACTOR_3MS (0 << 0)
+ # define DP_AG_FACTOR_2MS (1 << 0)
+ # define DP_AG_FACTOR_1MS (2 << 0)
+ # define DP_AG_FACTOR_500US (3 << 0)
+ # define DP_AG_FACTOR_200US (4 << 0)
+ # define DP_AG_FACTOR_100US (5 << 0)
+ # define DP_AG_FACTOR_10US (6 << 0)
+ # define DP_AG_FACTOR_1US (7 << 0)
+ # define DP_VG_FACTOR_MASK (0xf << 4)
+ # define DP_VG_FACTOR_3MS (0 << 4)
+ # define DP_VG_FACTOR_2MS (1 << 4)
+ # define DP_VG_FACTOR_1MS (2 << 4)
+ # define DP_VG_FACTOR_500US (3 << 4)
+ # define DP_VG_FACTOR_200US (4 << 4)
+ # define DP_VG_FACTOR_100US (5 << 4)
+
+ #define DP_AUD_DEC_LAT0 0x024
+ #define DP_AUD_DEC_LAT1 0x025
+
+ #define DP_AUD_PP_LAT0 0x026
+ #define DP_AUD_PP_LAT1 0x027
+
+ #define DP_VID_INTER_LAT 0x028
+
+ #define DP_VID_PROG_LAT 0x029
+
+ #define DP_REP_LAT 0x02a
+
+ #define DP_AUD_DEL_INS0 0x02b
+ #define DP_AUD_DEL_INS1 0x02c
+ #define DP_AUD_DEL_INS2 0x02d
+ /* End of AV_SYNC_DATA_BLOCK */
+
+ #define DP_RECEIVER_ALPM_CAP 0x02e /* eDP 1.4 */
+ # define DP_ALPM_CAP (1 << 0)
+
+ #define DP_SINK_DEVICE_AUX_FRAME_SYNC_CAP 0x02f /* eDP 1.4 */
+ # define DP_AUX_FRAME_SYNC_CAP (1 << 0)
+
+ #define DP_GUID 0x030 /* 1.2 */
+
+ #define DP_DSC_SUPPORT 0x060 /* DP 1.4 */
+ # define DP_DSC_DECOMPRESSION_IS_SUPPORTED (1 << 0)
+
+ #define DP_DSC_REV 0x061
+ # define DP_DSC_MAJOR_MASK (0xf << 0)
+ # define DP_DSC_MINOR_MASK (0xf << 4)
+ # define DP_DSC_MAJOR_SHIFT 0
+ # define DP_DSC_MINOR_SHIFT 4
+
+ #define DP_DSC_RC_BUF_BLK_SIZE 0x062
+ # define DP_DSC_RC_BUF_BLK_SIZE_1 0x0
+ # define DP_DSC_RC_BUF_BLK_SIZE_4 0x1
+ # define DP_DSC_RC_BUF_BLK_SIZE_16 0x2
+ # define DP_DSC_RC_BUF_BLK_SIZE_64 0x3
+
+ #define DP_DSC_RC_BUF_SIZE 0x063
+
+ #define DP_DSC_SLICE_CAP_1 0x064
+ # define DP_DSC_1_PER_DP_DSC_SINK (1 << 0)
+ # define DP_DSC_2_PER_DP_DSC_SINK (1 << 1)
+ # define DP_DSC_4_PER_DP_DSC_SINK (1 << 3)
+ # define DP_DSC_6_PER_DP_DSC_SINK (1 << 4)
+ # define DP_DSC_8_PER_DP_DSC_SINK (1 << 5)
+ # define DP_DSC_10_PER_DP_DSC_SINK (1 << 6)
+ # define DP_DSC_12_PER_DP_DSC_SINK (1 << 7)
+
+ #define DP_DSC_LINE_BUF_BIT_DEPTH 0x065
+ # define DP_DSC_LINE_BUF_BIT_DEPTH_MASK (0xf << 0)
+ # define DP_DSC_LINE_BUF_BIT_DEPTH_9 0x0
+ # define DP_DSC_LINE_BUF_BIT_DEPTH_10 0x1
+ # define DP_DSC_LINE_BUF_BIT_DEPTH_11 0x2
+ # define DP_DSC_LINE_BUF_BIT_DEPTH_12 0x3
+ # define DP_DSC_LINE_BUF_BIT_DEPTH_13 0x4
+ # define DP_DSC_LINE_BUF_BIT_DEPTH_14 0x5
+ # define DP_DSC_LINE_BUF_BIT_DEPTH_15 0x6
+ # define DP_DSC_LINE_BUF_BIT_DEPTH_16 0x7
+ # define DP_DSC_LINE_BUF_BIT_DEPTH_8 0x8
+
+ #define DP_DSC_BLK_PREDICTION_SUPPORT 0x066
+ # define DP_DSC_BLK_PREDICTION_IS_SUPPORTED (1 << 0)
+
+ #define DP_DSC_MAX_BITS_PER_PIXEL_LOW 0x067 /* eDP 1.4 */
+
+ #define DP_DSC_MAX_BITS_PER_PIXEL_HI 0x068 /* eDP 1.4 */
+ # define DP_DSC_MAX_BITS_PER_PIXEL_HI_MASK (0x3 << 0)
+ # define DP_DSC_MAX_BITS_PER_PIXEL_HI_SHIFT 8
+
+ #define DP_DSC_DEC_COLOR_FORMAT_CAP 0x069
+ # define DP_DSC_RGB (1 << 0)
+ # define DP_DSC_YCbCr444 (1 << 1)
+ # define DP_DSC_YCbCr422_Simple (1 << 2)
+ # define DP_DSC_YCbCr422_Native (1 << 3)
+ # define DP_DSC_YCbCr420_Native (1 << 4)
+
+ #define DP_DSC_DEC_COLOR_DEPTH_CAP 0x06A
+ # define DP_DSC_8_BPC (1 << 1)
+ # define DP_DSC_10_BPC (1 << 2)
+ # define DP_DSC_12_BPC (1 << 3)
+
+ #define DP_DSC_PEAK_THROUGHPUT 0x06B
+ # define DP_DSC_THROUGHPUT_MODE_0_MASK (0xf << 0)
+ # define DP_DSC_THROUGHPUT_MODE_0_SHIFT 0
+ # define DP_DSC_THROUGHPUT_MODE_0_UNSUPPORTED 0
+ # define DP_DSC_THROUGHPUT_MODE_0_340 (1 << 0)
+ # define DP_DSC_THROUGHPUT_MODE_0_400 (2 << 0)
+ # define DP_DSC_THROUGHPUT_MODE_0_450 (3 << 0)
+ # define DP_DSC_THROUGHPUT_MODE_0_500 (4 << 0)
+ # define DP_DSC_THROUGHPUT_MODE_0_550 (5 << 0)
+ # define DP_DSC_THROUGHPUT_MODE_0_600 (6 << 0)
+ # define DP_DSC_THROUGHPUT_MODE_0_650 (7 << 0)
+ # define DP_DSC_THROUGHPUT_MODE_0_700 (8 << 0)
+ # define DP_DSC_THROUGHPUT_MODE_0_750 (9 << 0)
+ # define DP_DSC_THROUGHPUT_MODE_0_800 (10 << 0)
+ # define DP_DSC_THROUGHPUT_MODE_0_850 (11 << 0)
+ # define DP_DSC_THROUGHPUT_MODE_0_900 (12 << 0)
+ # define DP_DSC_THROUGHPUT_MODE_0_950 (13 << 0)
+ # define DP_DSC_THROUGHPUT_MODE_0_1000 (14 << 0)
+ # define DP_DSC_THROUGHPUT_MODE_0_170 (15 << 0) /* 1.4a */
+ # define DP_DSC_THROUGHPUT_MODE_1_MASK (0xf << 4)
+ # define DP_DSC_THROUGHPUT_MODE_1_SHIFT 4
+ # define DP_DSC_THROUGHPUT_MODE_1_UNSUPPORTED 0
+ # define DP_DSC_THROUGHPUT_MODE_1_340 (1 << 4)
+ # define DP_DSC_THROUGHPUT_MODE_1_400 (2 << 4)
+ # define DP_DSC_THROUGHPUT_MODE_1_450 (3 << 4)
+ # define DP_DSC_THROUGHPUT_MODE_1_500 (4 << 4)
+ # define DP_DSC_THROUGHPUT_MODE_1_550 (5 << 4)
+ # define DP_DSC_THROUGHPUT_MODE_1_600 (6 << 4)
+ # define DP_DSC_THROUGHPUT_MODE_1_650 (7 << 4)
+ # define DP_DSC_THROUGHPUT_MODE_1_700 (8 << 4)
+ # define DP_DSC_THROUGHPUT_MODE_1_750 (9 << 4)
+ # define DP_DSC_THROUGHPUT_MODE_1_800 (10 << 4)
+ # define DP_DSC_THROUGHPUT_MODE_1_850 (11 << 4)
+ # define DP_DSC_THROUGHPUT_MODE_1_900 (12 << 4)
+ # define DP_DSC_THROUGHPUT_MODE_1_950 (13 << 4)
+ # define DP_DSC_THROUGHPUT_MODE_1_1000 (14 << 4)
+ # define DP_DSC_THROUGHPUT_MODE_1_170 (15 << 4)
+
+ #define DP_DSC_MAX_SLICE_WIDTH 0x06C
+ #define DP_DSC_MIN_SLICE_WIDTH_VALUE 2560
+ #define DP_DSC_SLICE_WIDTH_MULTIPLIER 320
+
+ #define DP_DSC_SLICE_CAP_2 0x06D
+ # define DP_DSC_16_PER_DP_DSC_SINK (1 << 0)
+ # define DP_DSC_20_PER_DP_DSC_SINK (1 << 1)
+ # define DP_DSC_24_PER_DP_DSC_SINK (1 << 2)
+
+ #define DP_DSC_BITS_PER_PIXEL_INC 0x06F
+ # define DP_DSC_BITS_PER_PIXEL_1_16 0x0
+ # define DP_DSC_BITS_PER_PIXEL_1_8 0x1
+ # define DP_DSC_BITS_PER_PIXEL_1_4 0x2
+ # define DP_DSC_BITS_PER_PIXEL_1_2 0x3
+ # define DP_DSC_BITS_PER_PIXEL_1 0x4
+
+ #define DP_PSR_SUPPORT 0x070 /* XXX 1.2? */
+ # define DP_PSR_IS_SUPPORTED 1
+ # define DP_PSR2_IS_SUPPORTED 2 /* eDP 1.4 */
+ # define DP_PSR2_WITH_Y_COORD_IS_SUPPORTED 3 /* eDP 1.4a */
+
+ #define DP_PSR_CAPS 0x071 /* XXX 1.2? */
+ # define DP_PSR_NO_TRAIN_ON_EXIT 1
+ # define DP_PSR_SETUP_TIME_330 (0 << 1)
+ # define DP_PSR_SETUP_TIME_275 (1 << 1)
+ # define DP_PSR_SETUP_TIME_220 (2 << 1)
+ # define DP_PSR_SETUP_TIME_165 (3 << 1)
+ # define DP_PSR_SETUP_TIME_110 (4 << 1)
+ # define DP_PSR_SETUP_TIME_55 (5 << 1)
+ # define DP_PSR_SETUP_TIME_0 (6 << 1)
+ # define DP_PSR_SETUP_TIME_MASK (7 << 1)
+ # define DP_PSR_SETUP_TIME_SHIFT 1
+ # define DP_PSR2_SU_Y_COORDINATE_REQUIRED (1 << 4) /* eDP 1.4a */
+ # define DP_PSR2_SU_GRANULARITY_REQUIRED (1 << 5) /* eDP 1.4b */
+
+ #define DP_PSR2_SU_X_GRANULARITY 0x072 /* eDP 1.4b */
+ #define DP_PSR2_SU_Y_GRANULARITY 0x074 /* eDP 1.4b */
+
+ /*
+ * 0x80-0x8f describe downstream port capabilities, but there are two layouts
+ * based on whether DP_DETAILED_CAP_INFO_AVAILABLE was set. If it was not,
+ * each port's descriptor is one byte wide. If it was set, each port's is
+ * four bytes wide, starting with the one byte from the base info. As of
+ * DP interop v1.1a only VGA defines additional detail.
+ */
+
+ /* offset 0 */
+ #define DP_DOWNSTREAM_PORT_0 0x80
+ # define DP_DS_PORT_TYPE_MASK (7 << 0)
+ # define DP_DS_PORT_TYPE_DP 0
+ # define DP_DS_PORT_TYPE_VGA 1
+ # define DP_DS_PORT_TYPE_DVI 2
+ # define DP_DS_PORT_TYPE_HDMI 3
+ # define DP_DS_PORT_TYPE_NON_EDID 4
+ # define DP_DS_PORT_TYPE_DP_DUALMODE 5
+ # define DP_DS_PORT_TYPE_WIRELESS 6
+ # define DP_DS_PORT_HPD (1 << 3)
+ # define DP_DS_NON_EDID_MASK (0xf << 4)
+ # define DP_DS_NON_EDID_720x480i_60 (1 << 4)
+ # define DP_DS_NON_EDID_720x480i_50 (2 << 4)
+ # define DP_DS_NON_EDID_1920x1080i_60 (3 << 4)
+ # define DP_DS_NON_EDID_1920x1080i_50 (4 << 4)
+ # define DP_DS_NON_EDID_1280x720_60 (5 << 4)
+ # define DP_DS_NON_EDID_1280x720_50 (7 << 4)
+ /* offset 1 for VGA is maximum megapixels per second / 8 */
+ /* offset 1 for DVI/HDMI is maximum TMDS clock in Mbps / 2.5 */
+ /* offset 2 for VGA/DVI/HDMI */
+ # define DP_DS_MAX_BPC_MASK (3 << 0)
+ # define DP_DS_8BPC 0
+ # define DP_DS_10BPC 1
+ # define DP_DS_12BPC 2
+ # define DP_DS_16BPC 3
+ /* HDMI2.1 PCON FRL CONFIGURATION */
+ # define DP_PCON_MAX_FRL_BW (7 << 2)
+ # define DP_PCON_MAX_0GBPS (0 << 2)
+ # define DP_PCON_MAX_9GBPS (1 << 2)
+ # define DP_PCON_MAX_18GBPS (2 << 2)
+ # define DP_PCON_MAX_24GBPS (3 << 2)
+ # define DP_PCON_MAX_32GBPS (4 << 2)
+ # define DP_PCON_MAX_40GBPS (5 << 2)
+ # define DP_PCON_MAX_48GBPS (6 << 2)
+ # define DP_PCON_SOURCE_CTL_MODE (1 << 5)
+
+ /* offset 3 for DVI */
+ # define DP_DS_DVI_DUAL_LINK (1 << 1)
+ # define DP_DS_DVI_HIGH_COLOR_DEPTH (1 << 2)
+ /* offset 3 for HDMI */
+ # define DP_DS_HDMI_FRAME_SEQ_TO_FRAME_PACK (1 << 0)
+ # define DP_DS_HDMI_YCBCR422_PASS_THROUGH (1 << 1)
+ # define DP_DS_HDMI_YCBCR420_PASS_THROUGH (1 << 2)
+ # define DP_DS_HDMI_YCBCR444_TO_422_CONV (1 << 3)
+ # define DP_DS_HDMI_YCBCR444_TO_420_CONV (1 << 4)
+
+ /*
+ * VESA DP-to-HDMI PCON Specification adds caps for colorspace
+ * conversion in DFP cap DPCD 83h. Sec6.1 Table-3.
+ * Based on the available support the source can enable
+ * color conversion by writing into PROTOCOL_COVERTER_CONTROL_2
+ * DPCD 3052h.
+ */
+ # define DP_DS_HDMI_BT601_RGB_YCBCR_CONV (1 << 5)
+ # define DP_DS_HDMI_BT709_RGB_YCBCR_CONV (1 << 6)
+ # define DP_DS_HDMI_BT2020_RGB_YCBCR_CONV (1 << 7)
+
+ #define DP_MAX_DOWNSTREAM_PORTS 0x10
+
+ /* DP Forward error Correction Registers */
+ #define DP_FEC_CAPABILITY 0x090 /* 1.4 */
+ # define DP_FEC_CAPABLE (1 << 0)
+ # define DP_FEC_UNCORR_BLK_ERROR_COUNT_CAP (1 << 1)
+ # define DP_FEC_CORR_BLK_ERROR_COUNT_CAP (1 << 2)
+ # define DP_FEC_BIT_ERROR_COUNT_CAP (1 << 3)
+ #define DP_FEC_CAPABILITY_1 0x091 /* 2.0 */
+
+ /* DP-HDMI2.1 PCON DSC ENCODER SUPPORT */
+ #define DP_PCON_DSC_ENCODER_CAP_SIZE 0xC /* 0x9E - 0x92 */
+ #define DP_PCON_DSC_ENCODER 0x092
+ # define DP_PCON_DSC_ENCODER_SUPPORTED (1 << 0)
+ # define DP_PCON_DSC_PPS_ENC_OVERRIDE (1 << 1)
+
+ /* DP-HDMI2.1 PCON DSC Version */
+ #define DP_PCON_DSC_VERSION 0x093
+ # define DP_PCON_DSC_MAJOR_MASK (0xF << 0)
+ # define DP_PCON_DSC_MINOR_MASK (0xF << 4)
+ # define DP_PCON_DSC_MAJOR_SHIFT 0
+ # define DP_PCON_DSC_MINOR_SHIFT 4
+
+ /* DP-HDMI2.1 PCON DSC RC Buffer block size */
+ #define DP_PCON_DSC_RC_BUF_BLK_INFO 0x094
+ # define DP_PCON_DSC_RC_BUF_BLK_SIZE (0x3 << 0)
+ # define DP_PCON_DSC_RC_BUF_BLK_1KB 0
+ # define DP_PCON_DSC_RC_BUF_BLK_4KB 1
+ # define DP_PCON_DSC_RC_BUF_BLK_16KB 2
+ # define DP_PCON_DSC_RC_BUF_BLK_64KB 3
+
+ /* DP-HDMI2.1 PCON DSC RC Buffer size */
+ #define DP_PCON_DSC_RC_BUF_SIZE 0x095
+
+ /* DP-HDMI2.1 PCON DSC Slice capabilities-1 */
+ #define DP_PCON_DSC_SLICE_CAP_1 0x096
+ # define DP_PCON_DSC_1_PER_DSC_ENC (0x1 << 0)
+ # define DP_PCON_DSC_2_PER_DSC_ENC (0x1 << 1)
+ # define DP_PCON_DSC_4_PER_DSC_ENC (0x1 << 3)
+ # define DP_PCON_DSC_6_PER_DSC_ENC (0x1 << 4)
+ # define DP_PCON_DSC_8_PER_DSC_ENC (0x1 << 5)
+ # define DP_PCON_DSC_10_PER_DSC_ENC (0x1 << 6)
+ # define DP_PCON_DSC_12_PER_DSC_ENC (0x1 << 7)
+
+ #define DP_PCON_DSC_BUF_BIT_DEPTH 0x097
+ # define DP_PCON_DSC_BIT_DEPTH_MASK (0xF << 0)
+ # define DP_PCON_DSC_DEPTH_9_BITS 0
+ # define DP_PCON_DSC_DEPTH_10_BITS 1
+ # define DP_PCON_DSC_DEPTH_11_BITS 2
+ # define DP_PCON_DSC_DEPTH_12_BITS 3
+ # define DP_PCON_DSC_DEPTH_13_BITS 4
+ # define DP_PCON_DSC_DEPTH_14_BITS 5
+ # define DP_PCON_DSC_DEPTH_15_BITS 6
+ # define DP_PCON_DSC_DEPTH_16_BITS 7
+ # define DP_PCON_DSC_DEPTH_8_BITS 8
+
+ #define DP_PCON_DSC_BLOCK_PREDICTION 0x098
+ # define DP_PCON_DSC_BLOCK_PRED_SUPPORT (0x1 << 0)
+
+ #define DP_PCON_DSC_ENC_COLOR_FMT_CAP 0x099
+ # define DP_PCON_DSC_ENC_RGB (0x1 << 0)
+ # define DP_PCON_DSC_ENC_YUV444 (0x1 << 1)
+ # define DP_PCON_DSC_ENC_YUV422_S (0x1 << 2)
+ # define DP_PCON_DSC_ENC_YUV422_N (0x1 << 3)
+ # define DP_PCON_DSC_ENC_YUV420_N (0x1 << 4)
+
+ #define DP_PCON_DSC_ENC_COLOR_DEPTH_CAP 0x09A
+ # define DP_PCON_DSC_ENC_8BPC (0x1 << 1)
+ # define DP_PCON_DSC_ENC_10BPC (0x1 << 2)
+ # define DP_PCON_DSC_ENC_12BPC (0x1 << 3)
+
+ #define DP_PCON_DSC_MAX_SLICE_WIDTH 0x09B
+
+ /* DP-HDMI2.1 PCON DSC Slice capabilities-2 */
+ #define DP_PCON_DSC_SLICE_CAP_2 0x09C
+ # define DP_PCON_DSC_16_PER_DSC_ENC (0x1 << 0)
+ # define DP_PCON_DSC_20_PER_DSC_ENC (0x1 << 1)
+ # define DP_PCON_DSC_24_PER_DSC_ENC (0x1 << 2)
+
+ /* DP-HDMI2.1 PCON HDMI TX Encoder Bits/pixel increment */
+ #define DP_PCON_DSC_BPP_INCR 0x09E
+ # define DP_PCON_DSC_BPP_INCR_MASK (0x7 << 0)
+ # define DP_PCON_DSC_ONE_16TH_BPP 0
+ # define DP_PCON_DSC_ONE_8TH_BPP 1
+ # define DP_PCON_DSC_ONE_4TH_BPP 2
+ # define DP_PCON_DSC_ONE_HALF_BPP 3
+ # define DP_PCON_DSC_ONE_BPP 4
+
+ /* DP Extended DSC Capabilities */
+ #define DP_DSC_BRANCH_OVERALL_THROUGHPUT_0 0x0a0 /* DP 1.4a SCR */
+ #define DP_DSC_BRANCH_OVERALL_THROUGHPUT_1 0x0a1
+ #define DP_DSC_BRANCH_MAX_LINE_WIDTH 0x0a2
+
+ /* DFP Capability Extension */
+ #define DP_DFP_CAPABILITY_EXTENSION_SUPPORT 0x0a3 /* 2.0 */
+
+ /* Link Configuration */
+ #define DP_LINK_BW_SET 0x100
+ # define DP_LINK_RATE_TABLE 0x00 /* eDP 1.4 */
+ # define DP_LINK_BW_1_62 0x06
+ # define DP_LINK_BW_2_7 0x0a
+ # define DP_LINK_BW_5_4 0x14 /* 1.2 */
+ # define DP_LINK_BW_8_1 0x1e /* 1.4 */
+ # define DP_LINK_BW_10 0x01 /* 2.0 128b/132b Link Layer */
+ # define DP_LINK_BW_13_5 0x04 /* 2.0 128b/132b Link Layer */
+ # define DP_LINK_BW_20 0x02 /* 2.0 128b/132b Link Layer */
+
+ #define DP_LANE_COUNT_SET 0x101
+ # define DP_LANE_COUNT_MASK 0x0f
+ # define DP_LANE_COUNT_ENHANCED_FRAME_EN (1 << 7)
+
+ #define DP_TRAINING_PATTERN_SET 0x102
+ # define DP_TRAINING_PATTERN_DISABLE 0
+ # define DP_TRAINING_PATTERN_1 1
+ # define DP_TRAINING_PATTERN_2 2
+ # define DP_TRAINING_PATTERN_3 3 /* 1.2 */
+ # define DP_TRAINING_PATTERN_4 7 /* 1.4 */
+ # define DP_TRAINING_PATTERN_MASK 0x3
+ # define DP_TRAINING_PATTERN_MASK_1_4 0xf
+
+ /* DPCD 1.1 only. For DPCD >= 1.2 see per-lane DP_LINK_QUAL_LANEn_SET */
+ # define DP_LINK_QUAL_PATTERN_11_DISABLE (0 << 2)
+ # define DP_LINK_QUAL_PATTERN_11_D10_2 (1 << 2)
+ # define DP_LINK_QUAL_PATTERN_11_ERROR_RATE (2 << 2)
+ # define DP_LINK_QUAL_PATTERN_11_PRBS7 (3 << 2)
+ # define DP_LINK_QUAL_PATTERN_11_MASK (3 << 2)
+
+ # define DP_RECOVERED_CLOCK_OUT_EN (1 << 4)
+ # define DP_LINK_SCRAMBLING_DISABLE (1 << 5)
+
+ # define DP_SYMBOL_ERROR_COUNT_BOTH (0 << 6)
+ # define DP_SYMBOL_ERROR_COUNT_DISPARITY (1 << 6)
+ # define DP_SYMBOL_ERROR_COUNT_SYMBOL (2 << 6)
+ # define DP_SYMBOL_ERROR_COUNT_MASK (3 << 6)
+
+ #define DP_TRAINING_LANE0_SET 0x103
+ #define DP_TRAINING_LANE1_SET 0x104
+ #define DP_TRAINING_LANE2_SET 0x105
+ #define DP_TRAINING_LANE3_SET 0x106
+
+ # define DP_TRAIN_VOLTAGE_SWING_MASK 0x3
+ # define DP_TRAIN_VOLTAGE_SWING_SHIFT 0
+ # define DP_TRAIN_MAX_SWING_REACHED (1 << 2)
+ # define DP_TRAIN_VOLTAGE_SWING_LEVEL_0 (0 << 0)
+ # define DP_TRAIN_VOLTAGE_SWING_LEVEL_1 (1 << 0)
+ # define DP_TRAIN_VOLTAGE_SWING_LEVEL_2 (2 << 0)
+ # define DP_TRAIN_VOLTAGE_SWING_LEVEL_3 (3 << 0)
+
+ # define DP_TRAIN_PRE_EMPHASIS_MASK (3 << 3)
+ # define DP_TRAIN_PRE_EMPH_LEVEL_0 (0 << 3)
+ # define DP_TRAIN_PRE_EMPH_LEVEL_1 (1 << 3)
+ # define DP_TRAIN_PRE_EMPH_LEVEL_2 (2 << 3)
+ # define DP_TRAIN_PRE_EMPH_LEVEL_3 (3 << 3)
+
+ # define DP_TRAIN_PRE_EMPHASIS_SHIFT 3
+ # define DP_TRAIN_MAX_PRE_EMPHASIS_REACHED (1 << 5)
+
+ # define DP_TX_FFE_PRESET_VALUE_MASK (0xf << 0) /* 2.0 128b/132b Link Layer */
+
+ #define DP_DOWNSPREAD_CTRL 0x107
+ # define DP_SPREAD_AMP_0_5 (1 << 4)
+ # define DP_MSA_TIMING_PAR_IGNORE_EN (1 << 7) /* eDP */
+
+ #define DP_MAIN_LINK_CHANNEL_CODING_SET 0x108
+ # define DP_SET_ANSI_8B10B (1 << 0)
+ # define DP_SET_ANSI_128B132B (1 << 1)
+
+ #define DP_I2C_SPEED_CONTROL_STATUS 0x109 /* DPI */
+ /* bitmask as for DP_I2C_SPEED_CAP */
+
+ #define DP_EDP_CONFIGURATION_SET 0x10a /* XXX 1.2? */
+ # define DP_ALTERNATE_SCRAMBLER_RESET_ENABLE (1 << 0)
+ # define DP_FRAMING_CHANGE_ENABLE (1 << 1)
+ # define DP_PANEL_SELF_TEST_ENABLE (1 << 7)
+
+ #define DP_LINK_QUAL_LANE0_SET 0x10b /* DPCD >= 1.2 */
+ #define DP_LINK_QUAL_LANE1_SET 0x10c
+ #define DP_LINK_QUAL_LANE2_SET 0x10d
+ #define DP_LINK_QUAL_LANE3_SET 0x10e
+ # define DP_LINK_QUAL_PATTERN_DISABLE 0
+ # define DP_LINK_QUAL_PATTERN_D10_2 1
+ # define DP_LINK_QUAL_PATTERN_ERROR_RATE 2
+ # define DP_LINK_QUAL_PATTERN_PRBS7 3
+ # define DP_LINK_QUAL_PATTERN_80BIT_CUSTOM 4
+ # define DP_LINK_QUAL_PATTERN_CP2520_PAT_1 5
+ # define DP_LINK_QUAL_PATTERN_CP2520_PAT_2 6
+ # define DP_LINK_QUAL_PATTERN_CP2520_PAT_3 7
+ /* DP 2.0 UHBR10, UHBR13.5, UHBR20 */
+ # define DP_LINK_QUAL_PATTERN_128B132B_TPS1 0x08
+ # define DP_LINK_QUAL_PATTERN_128B132B_TPS2 0x10
+ # define DP_LINK_QUAL_PATTERN_PRSBS9 0x18
+ # define DP_LINK_QUAL_PATTERN_PRSBS11 0x20
+ # define DP_LINK_QUAL_PATTERN_PRSBS15 0x28
+ # define DP_LINK_QUAL_PATTERN_PRSBS23 0x30
+ # define DP_LINK_QUAL_PATTERN_PRSBS31 0x38
+ # define DP_LINK_QUAL_PATTERN_CUSTOM 0x40
+ # define DP_LINK_QUAL_PATTERN_SQUARE 0x48
+
+ #define DP_TRAINING_LANE0_1_SET2 0x10f
+ #define DP_TRAINING_LANE2_3_SET2 0x110
+ # define DP_LANE02_POST_CURSOR2_SET_MASK (3 << 0)
+ # define DP_LANE02_MAX_POST_CURSOR2_REACHED (1 << 2)
+ # define DP_LANE13_POST_CURSOR2_SET_MASK (3 << 4)
+ # define DP_LANE13_MAX_POST_CURSOR2_REACHED (1 << 6)
+
+ #define DP_MSTM_CTRL 0x111 /* 1.2 */
+ # define DP_MST_EN (1 << 0)
+ # define DP_UP_REQ_EN (1 << 1)
+ # define DP_UPSTREAM_IS_SRC (1 << 2)
+
+ #define DP_AUDIO_DELAY0 0x112 /* 1.2 */
+ #define DP_AUDIO_DELAY1 0x113
+ #define DP_AUDIO_DELAY2 0x114
+
+ #define DP_LINK_RATE_SET 0x115 /* eDP 1.4 */
+ # define DP_LINK_RATE_SET_SHIFT 0
+ # define DP_LINK_RATE_SET_MASK (7 << 0)
+
+ #define DP_RECEIVER_ALPM_CONFIG 0x116 /* eDP 1.4 */
+ # define DP_ALPM_ENABLE (1 << 0)
+ # define DP_ALPM_LOCK_ERROR_IRQ_HPD_ENABLE (1 << 1)
+
+ #define DP_SINK_DEVICE_AUX_FRAME_SYNC_CONF 0x117 /* eDP 1.4 */
+ # define DP_AUX_FRAME_SYNC_ENABLE (1 << 0)
+ # define DP_IRQ_HPD_ENABLE (1 << 1)
+
+ #define DP_UPSTREAM_DEVICE_DP_PWR_NEED 0x118 /* 1.2 */
+ # define DP_PWR_NOT_NEEDED (1 << 0)
+
+ #define DP_FEC_CONFIGURATION 0x120 /* 1.4 */
+ # define DP_FEC_READY (1 << 0)
+ # define DP_FEC_ERR_COUNT_SEL_MASK (7 << 1)
+ # define DP_FEC_ERR_COUNT_DIS (0 << 1)
+ # define DP_FEC_UNCORR_BLK_ERROR_COUNT (1 << 1)
+ # define DP_FEC_CORR_BLK_ERROR_COUNT (2 << 1)
+ # define DP_FEC_BIT_ERROR_COUNT (3 << 1)
+ # define DP_FEC_LANE_SELECT_MASK (3 << 4)
+ # define DP_FEC_LANE_0_SELECT (0 << 4)
+ # define DP_FEC_LANE_1_SELECT (1 << 4)
+ # define DP_FEC_LANE_2_SELECT (2 << 4)
+ # define DP_FEC_LANE_3_SELECT (3 << 4)
+
+ #define DP_AUX_FRAME_SYNC_VALUE 0x15c /* eDP 1.4 */
+ # define DP_AUX_FRAME_SYNC_VALID (1 << 0)
+
+ #define DP_DSC_ENABLE 0x160 /* DP 1.4 */
+ # define DP_DECOMPRESSION_EN (1 << 0)
+ #define DP_DSC_CONFIGURATION 0x161 /* DP 2.0 */
+
+ #define DP_PSR_EN_CFG 0x170 /* XXX 1.2? */
+ # define DP_PSR_ENABLE BIT(0)
+ # define DP_PSR_MAIN_LINK_ACTIVE BIT(1)
+ # define DP_PSR_CRC_VERIFICATION BIT(2)
+ # define DP_PSR_FRAME_CAPTURE BIT(3)
+ # define DP_PSR_SU_REGION_SCANLINE_CAPTURE BIT(4) /* eDP 1.4a */
+ # define DP_PSR_IRQ_HPD_WITH_CRC_ERRORS BIT(5) /* eDP 1.4a */
+ # define DP_PSR_ENABLE_PSR2 BIT(6) /* eDP 1.4a */
+
+ #define DP_ADAPTER_CTRL 0x1a0
+ # define DP_ADAPTER_CTRL_FORCE_LOAD_SENSE (1 << 0)
+
+ #define DP_BRANCH_DEVICE_CTRL 0x1a1
+ # define DP_BRANCH_DEVICE_IRQ_HPD (1 << 0)
+
+ #define DP_PAYLOAD_ALLOCATE_SET 0x1c0
+ #define DP_PAYLOAD_ALLOCATE_START_TIME_SLOT 0x1c1
+ #define DP_PAYLOAD_ALLOCATE_TIME_SLOT_COUNT 0x1c2
+
+ /* Link/Sink Device Status */
+ #define DP_SINK_COUNT 0x200
+ /* prior to 1.2 bit 7 was reserved mbz */
+ # define DP_GET_SINK_COUNT(x) ((((x) & 0x80) >> 1) | ((x) & 0x3f))
+ # define DP_SINK_CP_READY (1 << 6)
+
+ #define DP_DEVICE_SERVICE_IRQ_VECTOR 0x201
+ # define DP_REMOTE_CONTROL_COMMAND_PENDING (1 << 0)
+ # define DP_AUTOMATED_TEST_REQUEST (1 << 1)
+ # define DP_CP_IRQ (1 << 2)
+ # define DP_MCCS_IRQ (1 << 3)
+ # define DP_DOWN_REP_MSG_RDY (1 << 4) /* 1.2 MST */
+ # define DP_UP_REQ_MSG_RDY (1 << 5) /* 1.2 MST */
+ # define DP_SINK_SPECIFIC_IRQ (1 << 6)
+
+ #define DP_LANE0_1_STATUS 0x202
+ #define DP_LANE2_3_STATUS 0x203
+ # define DP_LANE_CR_DONE (1 << 0)
+ # define DP_LANE_CHANNEL_EQ_DONE (1 << 1)
+ # define DP_LANE_SYMBOL_LOCKED (1 << 2)
+
+ #define DP_CHANNEL_EQ_BITS (DP_LANE_CR_DONE | \
+ DP_LANE_CHANNEL_EQ_DONE | \
+ DP_LANE_SYMBOL_LOCKED)
+
+ #define DP_LANE_ALIGN_STATUS_UPDATED 0x204
+
+ #define DP_INTERLANE_ALIGN_DONE (1 << 0)
+ #define DP_DOWNSTREAM_PORT_STATUS_CHANGED (1 << 6)
+ #define DP_LINK_STATUS_UPDATED (1 << 7)
+
+ #define DP_SINK_STATUS 0x205
+ # define DP_RECEIVE_PORT_0_STATUS (1 << 0)
+ # define DP_RECEIVE_PORT_1_STATUS (1 << 1)
+ # define DP_STREAM_REGENERATION_STATUS (1 << 2) /* 2.0 */
+ # define DP_INTRA_HOP_AUX_REPLY_INDICATION (1 << 3) /* 2.0 */
+
+ #define DP_ADJUST_REQUEST_LANE0_1 0x206
+ #define DP_ADJUST_REQUEST_LANE2_3 0x207
+ # define DP_ADJUST_VOLTAGE_SWING_LANE0_MASK 0x03
+ # define DP_ADJUST_VOLTAGE_SWING_LANE0_SHIFT 0
+ # define DP_ADJUST_PRE_EMPHASIS_LANE0_MASK 0x0c
+ # define DP_ADJUST_PRE_EMPHASIS_LANE0_SHIFT 2
+ # define DP_ADJUST_VOLTAGE_SWING_LANE1_MASK 0x30
+ # define DP_ADJUST_VOLTAGE_SWING_LANE1_SHIFT 4
+ # define DP_ADJUST_PRE_EMPHASIS_LANE1_MASK 0xc0
+ # define DP_ADJUST_PRE_EMPHASIS_LANE1_SHIFT 6
+
+ /* DP 2.0 128b/132b Link Layer */
+ # define DP_ADJUST_TX_FFE_PRESET_LANE0_MASK (0xf << 0)
+ # define DP_ADJUST_TX_FFE_PRESET_LANE0_SHIFT 0
+ # define DP_ADJUST_TX_FFE_PRESET_LANE1_MASK (0xf << 4)
+ # define DP_ADJUST_TX_FFE_PRESET_LANE1_SHIFT 4
+
+ #define DP_ADJUST_REQUEST_POST_CURSOR2 0x20c
+ # define DP_ADJUST_POST_CURSOR2_LANE0_MASK 0x03
+ # define DP_ADJUST_POST_CURSOR2_LANE0_SHIFT 0
+ # define DP_ADJUST_POST_CURSOR2_LANE1_MASK 0x0c
+ # define DP_ADJUST_POST_CURSOR2_LANE1_SHIFT 2
+ # define DP_ADJUST_POST_CURSOR2_LANE2_MASK 0x30
+ # define DP_ADJUST_POST_CURSOR2_LANE2_SHIFT 4
+ # define DP_ADJUST_POST_CURSOR2_LANE3_MASK 0xc0
+ # define DP_ADJUST_POST_CURSOR2_LANE3_SHIFT 6
+
+ #define DP_TEST_REQUEST 0x218
+ # define DP_TEST_LINK_TRAINING (1 << 0)
+ # define DP_TEST_LINK_VIDEO_PATTERN (1 << 1)
+ # define DP_TEST_LINK_EDID_READ (1 << 2)
+ # define DP_TEST_LINK_PHY_TEST_PATTERN (1 << 3) /* DPCD >= 1.1 */
+ # define DP_TEST_LINK_FAUX_PATTERN (1 << 4) /* DPCD >= 1.2 */
+ # define DP_TEST_LINK_AUDIO_PATTERN (1 << 5) /* DPCD >= 1.2 */
+ # define DP_TEST_LINK_AUDIO_DISABLED_VIDEO (1 << 6) /* DPCD >= 1.2 */
+
+ #define DP_TEST_LINK_RATE 0x219
+ # define DP_LINK_RATE_162 (0x6)
+ # define DP_LINK_RATE_27 (0xa)
+
+ #define DP_TEST_LANE_COUNT 0x220
+
+ #define DP_TEST_PATTERN 0x221
+ # define DP_NO_TEST_PATTERN 0x0
+ # define DP_COLOR_RAMP 0x1
+ # define DP_BLACK_AND_WHITE_VERTICAL_LINES 0x2
+ # define DP_COLOR_SQUARE 0x3
+
+ #define DP_TEST_H_TOTAL_HI 0x222
+ #define DP_TEST_H_TOTAL_LO 0x223
+
+ #define DP_TEST_V_TOTAL_HI 0x224
+ #define DP_TEST_V_TOTAL_LO 0x225
+
+ #define DP_TEST_H_START_HI 0x226
+ #define DP_TEST_H_START_LO 0x227
+
+ #define DP_TEST_V_START_HI 0x228
+ #define DP_TEST_V_START_LO 0x229
+
+ #define DP_TEST_HSYNC_HI 0x22A
+ # define DP_TEST_HSYNC_POLARITY (1 << 7)
+ # define DP_TEST_HSYNC_WIDTH_HI_MASK (127 << 0)
+ #define DP_TEST_HSYNC_WIDTH_LO 0x22B
+
+ #define DP_TEST_VSYNC_HI 0x22C
+ # define DP_TEST_VSYNC_POLARITY (1 << 7)
+ # define DP_TEST_VSYNC_WIDTH_HI_MASK (127 << 0)
+ #define DP_TEST_VSYNC_WIDTH_LO 0x22D
+
+ #define DP_TEST_H_WIDTH_HI 0x22E
+ #define DP_TEST_H_WIDTH_LO 0x22F
+
+ #define DP_TEST_V_HEIGHT_HI 0x230
+ #define DP_TEST_V_HEIGHT_LO 0x231
+
+ #define DP_TEST_MISC0 0x232
+ # define DP_TEST_SYNC_CLOCK (1 << 0)
+ # define DP_TEST_COLOR_FORMAT_MASK (3 << 1)
+ # define DP_TEST_COLOR_FORMAT_SHIFT 1
+ # define DP_COLOR_FORMAT_RGB (0 << 1)
+ # define DP_COLOR_FORMAT_YCbCr422 (1 << 1)
+ # define DP_COLOR_FORMAT_YCbCr444 (2 << 1)
+ # define DP_TEST_DYNAMIC_RANGE_VESA (0 << 3)
+ # define DP_TEST_DYNAMIC_RANGE_CEA (1 << 3)
+ # define DP_TEST_YCBCR_COEFFICIENTS (1 << 4)
+ # define DP_YCBCR_COEFFICIENTS_ITU601 (0 << 4)
+ # define DP_YCBCR_COEFFICIENTS_ITU709 (1 << 4)
+ # define DP_TEST_BIT_DEPTH_MASK (7 << 5)
+ # define DP_TEST_BIT_DEPTH_SHIFT 5
+ # define DP_TEST_BIT_DEPTH_6 (0 << 5)
+ # define DP_TEST_BIT_DEPTH_8 (1 << 5)
+ # define DP_TEST_BIT_DEPTH_10 (2 << 5)
+ # define DP_TEST_BIT_DEPTH_12 (3 << 5)
+ # define DP_TEST_BIT_DEPTH_16 (4 << 5)
+
+ #define DP_TEST_MISC1 0x233
+ # define DP_TEST_REFRESH_DENOMINATOR (1 << 0)
+ # define DP_TEST_INTERLACED (1 << 1)
+
+ #define DP_TEST_REFRESH_RATE_NUMERATOR 0x234
+
+ #define DP_TEST_MISC0 0x232
+
+ #define DP_TEST_CRC_R_CR 0x240
+ #define DP_TEST_CRC_G_Y 0x242
+ #define DP_TEST_CRC_B_CB 0x244
+
+ #define DP_TEST_SINK_MISC 0x246
+ # define DP_TEST_CRC_SUPPORTED (1 << 5)
+ # define DP_TEST_COUNT_MASK 0xf
+
+ #define DP_PHY_TEST_PATTERN 0x248
+ # define DP_PHY_TEST_PATTERN_SEL_MASK 0x7
+ # define DP_PHY_TEST_PATTERN_NONE 0x0
+ # define DP_PHY_TEST_PATTERN_D10_2 0x1
+ # define DP_PHY_TEST_PATTERN_ERROR_COUNT 0x2
+ # define DP_PHY_TEST_PATTERN_PRBS7 0x3
+ # define DP_PHY_TEST_PATTERN_80BIT_CUSTOM 0x4
+ # define DP_PHY_TEST_PATTERN_CP2520 0x5
+
+ #define DP_PHY_SQUARE_PATTERN 0x249
+
+ #define DP_TEST_HBR2_SCRAMBLER_RESET 0x24A
+ #define DP_TEST_80BIT_CUSTOM_PATTERN_7_0 0x250
+ #define DP_TEST_80BIT_CUSTOM_PATTERN_15_8 0x251
+ #define DP_TEST_80BIT_CUSTOM_PATTERN_23_16 0x252
+ #define DP_TEST_80BIT_CUSTOM_PATTERN_31_24 0x253
+ #define DP_TEST_80BIT_CUSTOM_PATTERN_39_32 0x254
+ #define DP_TEST_80BIT_CUSTOM_PATTERN_47_40 0x255
+ #define DP_TEST_80BIT_CUSTOM_PATTERN_55_48 0x256
+ #define DP_TEST_80BIT_CUSTOM_PATTERN_63_56 0x257
+ #define DP_TEST_80BIT_CUSTOM_PATTERN_71_64 0x258
+ #define DP_TEST_80BIT_CUSTOM_PATTERN_79_72 0x259
+
+ #define DP_TEST_RESPONSE 0x260
+ # define DP_TEST_ACK (1 << 0)
+ # define DP_TEST_NAK (1 << 1)
+ # define DP_TEST_EDID_CHECKSUM_WRITE (1 << 2)
+
+ #define DP_TEST_EDID_CHECKSUM 0x261
+
+ #define DP_TEST_SINK 0x270
+ # define DP_TEST_SINK_START (1 << 0)
+ #define DP_TEST_AUDIO_MODE 0x271
+ #define DP_TEST_AUDIO_PATTERN_TYPE 0x272
+ #define DP_TEST_AUDIO_PERIOD_CH1 0x273
+ #define DP_TEST_AUDIO_PERIOD_CH2 0x274
+ #define DP_TEST_AUDIO_PERIOD_CH3 0x275
+ #define DP_TEST_AUDIO_PERIOD_CH4 0x276
+ #define DP_TEST_AUDIO_PERIOD_CH5 0x277
+ #define DP_TEST_AUDIO_PERIOD_CH6 0x278
+ #define DP_TEST_AUDIO_PERIOD_CH7 0x279
+ #define DP_TEST_AUDIO_PERIOD_CH8 0x27A
+
+ #define DP_FEC_STATUS 0x280 /* 1.4 */
+ # define DP_FEC_DECODE_EN_DETECTED (1 << 0)
+ # define DP_FEC_DECODE_DIS_DETECTED (1 << 1)
+
+ #define DP_FEC_ERROR_COUNT_LSB 0x0281 /* 1.4 */
+
+ #define DP_FEC_ERROR_COUNT_MSB 0x0282 /* 1.4 */
+ # define DP_FEC_ERROR_COUNT_MASK 0x7F
+ # define DP_FEC_ERR_COUNT_VALID (1 << 7)
+
+ #define DP_PAYLOAD_TABLE_UPDATE_STATUS 0x2c0 /* 1.2 MST */
+ # define DP_PAYLOAD_TABLE_UPDATED (1 << 0)
+ # define DP_PAYLOAD_ACT_HANDLED (1 << 1)
+
+ #define DP_VC_PAYLOAD_ID_SLOT_1 0x2c1 /* 1.2 MST */
+ /* up to ID_SLOT_63 at 0x2ff */
+
+ /* Source Device-specific */
+ #define DP_SOURCE_OUI 0x300
+
+ /* Sink Device-specific */
+ #define DP_SINK_OUI 0x400
+
+ /* Branch Device-specific */
+ #define DP_BRANCH_OUI 0x500
+ #define DP_BRANCH_ID 0x503
+ #define DP_BRANCH_REVISION_START 0x509
+ #define DP_BRANCH_HW_REV 0x509
+ #define DP_BRANCH_SW_REV 0x50A
+
+ /* Link/Sink Device Power Control */
+ #define DP_SET_POWER 0x600
+ # define DP_SET_POWER_D0 0x1
+ # define DP_SET_POWER_D3 0x2
+ # define DP_SET_POWER_MASK 0x3
+ # define DP_SET_POWER_D3_AUX_ON 0x5
+
+ /* eDP-specific */
+ #define DP_EDP_DPCD_REV 0x700 /* eDP 1.2 */
+ # define DP_EDP_11 0x00
+ # define DP_EDP_12 0x01
+ # define DP_EDP_13 0x02
+ # define DP_EDP_14 0x03
+ # define DP_EDP_14a 0x04 /* eDP 1.4a */
+ # define DP_EDP_14b 0x05 /* eDP 1.4b */
+
+ #define DP_EDP_GENERAL_CAP_1 0x701
+ # define DP_EDP_TCON_BACKLIGHT_ADJUSTMENT_CAP (1 << 0)
+ # define DP_EDP_BACKLIGHT_PIN_ENABLE_CAP (1 << 1)
+ # define DP_EDP_BACKLIGHT_AUX_ENABLE_CAP (1 << 2)
+ # define DP_EDP_PANEL_SELF_TEST_PIN_ENABLE_CAP (1 << 3)
+ # define DP_EDP_PANEL_SELF_TEST_AUX_ENABLE_CAP (1 << 4)
+ # define DP_EDP_FRC_ENABLE_CAP (1 << 5)
+ # define DP_EDP_COLOR_ENGINE_CAP (1 << 6)
+ # define DP_EDP_SET_POWER_CAP (1 << 7)
+
+ #define DP_EDP_BACKLIGHT_ADJUSTMENT_CAP 0x702
+ # define DP_EDP_BACKLIGHT_BRIGHTNESS_PWM_PIN_CAP (1 << 0)
+ # define DP_EDP_BACKLIGHT_BRIGHTNESS_AUX_SET_CAP (1 << 1)
+ # define DP_EDP_BACKLIGHT_BRIGHTNESS_BYTE_COUNT (1 << 2)
+ # define DP_EDP_BACKLIGHT_AUX_PWM_PRODUCT_CAP (1 << 3)
+ # define DP_EDP_BACKLIGHT_FREQ_PWM_PIN_PASSTHRU_CAP (1 << 4)
+ # define DP_EDP_BACKLIGHT_FREQ_AUX_SET_CAP (1 << 5)
+ # define DP_EDP_DYNAMIC_BACKLIGHT_CAP (1 << 6)
+ # define DP_EDP_VBLANK_BACKLIGHT_UPDATE_CAP (1 << 7)
+
+ #define DP_EDP_GENERAL_CAP_2 0x703
+ # define DP_EDP_OVERDRIVE_ENGINE_ENABLED (1 << 0)
+
+ #define DP_EDP_GENERAL_CAP_3 0x704 /* eDP 1.4 */
+ # define DP_EDP_X_REGION_CAP_MASK (0xf << 0)
+ # define DP_EDP_X_REGION_CAP_SHIFT 0
+ # define DP_EDP_Y_REGION_CAP_MASK (0xf << 4)
+ # define DP_EDP_Y_REGION_CAP_SHIFT 4
+
+ #define DP_EDP_DISPLAY_CONTROL_REGISTER 0x720
+ # define DP_EDP_BACKLIGHT_ENABLE (1 << 0)
+ # define DP_EDP_BLACK_VIDEO_ENABLE (1 << 1)
+ # define DP_EDP_FRC_ENABLE (1 << 2)
+ # define DP_EDP_COLOR_ENGINE_ENABLE (1 << 3)
+ # define DP_EDP_VBLANK_BACKLIGHT_UPDATE_ENABLE (1 << 7)
+
+ #define DP_EDP_BACKLIGHT_MODE_SET_REGISTER 0x721
+ # define DP_EDP_BACKLIGHT_CONTROL_MODE_MASK (3 << 0)
+ # define DP_EDP_BACKLIGHT_CONTROL_MODE_PWM (0 << 0)
+ # define DP_EDP_BACKLIGHT_CONTROL_MODE_PRESET (1 << 0)
+ # define DP_EDP_BACKLIGHT_CONTROL_MODE_DPCD (2 << 0)
+ # define DP_EDP_BACKLIGHT_CONTROL_MODE_PRODUCT (3 << 0)
+ # define DP_EDP_BACKLIGHT_FREQ_PWM_PIN_PASSTHRU_ENABLE (1 << 2)
+ # define DP_EDP_BACKLIGHT_FREQ_AUX_SET_ENABLE (1 << 3)
+ # define DP_EDP_DYNAMIC_BACKLIGHT_ENABLE (1 << 4)
+ # define DP_EDP_REGIONAL_BACKLIGHT_ENABLE (1 << 5)
+ # define DP_EDP_UPDATE_REGION_BRIGHTNESS (1 << 6) /* eDP 1.4 */
+
+ #define DP_EDP_BACKLIGHT_BRIGHTNESS_MSB 0x722
+ #define DP_EDP_BACKLIGHT_BRIGHTNESS_LSB 0x723
+
+ #define DP_EDP_PWMGEN_BIT_COUNT 0x724
+ #define DP_EDP_PWMGEN_BIT_COUNT_CAP_MIN 0x725
+ #define DP_EDP_PWMGEN_BIT_COUNT_CAP_MAX 0x726
+ # define DP_EDP_PWMGEN_BIT_COUNT_MASK (0x1f << 0)
+
+ #define DP_EDP_BACKLIGHT_CONTROL_STATUS 0x727
+
+ #define DP_EDP_BACKLIGHT_FREQ_SET 0x728
+ # define DP_EDP_BACKLIGHT_FREQ_BASE_KHZ 27000
+
+ #define DP_EDP_BACKLIGHT_FREQ_CAP_MIN_MSB 0x72a
+ #define DP_EDP_BACKLIGHT_FREQ_CAP_MIN_MID 0x72b
+ #define DP_EDP_BACKLIGHT_FREQ_CAP_MIN_LSB 0x72c
+
+ #define DP_EDP_BACKLIGHT_FREQ_CAP_MAX_MSB 0x72d
+ #define DP_EDP_BACKLIGHT_FREQ_CAP_MAX_MID 0x72e
+ #define DP_EDP_BACKLIGHT_FREQ_CAP_MAX_LSB 0x72f
+
+ #define DP_EDP_DBC_MINIMUM_BRIGHTNESS_SET 0x732
+ #define DP_EDP_DBC_MAXIMUM_BRIGHTNESS_SET 0x733
+
+ #define DP_EDP_REGIONAL_BACKLIGHT_BASE 0x740 /* eDP 1.4 */
+ #define DP_EDP_REGIONAL_BACKLIGHT_0 0x741 /* eDP 1.4 */
+
+ #define DP_EDP_MSO_LINK_CAPABILITIES 0x7a4 /* eDP 1.4 */
+ # define DP_EDP_MSO_NUMBER_OF_LINKS_MASK (7 << 0)
+ # define DP_EDP_MSO_NUMBER_OF_LINKS_SHIFT 0
+ # define DP_EDP_MSO_INDEPENDENT_LINK_BIT (1 << 3)
+
+ /* Sideband MSG Buffers */
+ #define DP_SIDEBAND_MSG_DOWN_REQ_BASE 0x1000 /* 1.2 MST */
+ #define DP_SIDEBAND_MSG_UP_REP_BASE 0x1200 /* 1.2 MST */
+ #define DP_SIDEBAND_MSG_DOWN_REP_BASE 0x1400 /* 1.2 MST */
+ #define DP_SIDEBAND_MSG_UP_REQ_BASE 0x1600 /* 1.2 MST */
+
+ /* DPRX Event Status Indicator */
+ #define DP_SINK_COUNT_ESI 0x2002 /* same as 0x200 */
+ #define DP_DEVICE_SERVICE_IRQ_VECTOR_ESI0 0x2003 /* same as 0x201 */
+
+ #define DP_DEVICE_SERVICE_IRQ_VECTOR_ESI1 0x2004 /* 1.2 */
+ # define DP_RX_GTC_MSTR_REQ_STATUS_CHANGE (1 << 0)
+ # define DP_LOCK_ACQUISITION_REQUEST (1 << 1)
+ # define DP_CEC_IRQ (1 << 2)
+
+ #define DP_LINK_SERVICE_IRQ_VECTOR_ESI0 0x2005 /* 1.2 */
+ # define RX_CAP_CHANGED (1 << 0)
+ # define LINK_STATUS_CHANGED (1 << 1)
+ # define STREAM_STATUS_CHANGED (1 << 2)
+ # define HDMI_LINK_STATUS_CHANGED (1 << 3)
+ # define CONNECTED_OFF_ENTRY_REQUESTED (1 << 4)
+
+ #define DP_PSR_ERROR_STATUS 0x2006 /* XXX 1.2? */
+ # define DP_PSR_LINK_CRC_ERROR (1 << 0)
+ # define DP_PSR_RFB_STORAGE_ERROR (1 << 1)
+ # define DP_PSR_VSC_SDP_UNCORRECTABLE_ERROR (1 << 2) /* eDP 1.4 */
+
+ #define DP_PSR_ESI 0x2007 /* XXX 1.2? */
+ # define DP_PSR_CAPS_CHANGE (1 << 0)
+
+ #define DP_PSR_STATUS 0x2008 /* XXX 1.2? */
+ # define DP_PSR_SINK_INACTIVE 0
+ # define DP_PSR_SINK_ACTIVE_SRC_SYNCED 1
+ # define DP_PSR_SINK_ACTIVE_RFB 2
+ # define DP_PSR_SINK_ACTIVE_SINK_SYNCED 3
+ # define DP_PSR_SINK_ACTIVE_RESYNC 4
+ # define DP_PSR_SINK_INTERNAL_ERROR 7
+ # define DP_PSR_SINK_STATE_MASK 0x07
+
+ #define DP_SYNCHRONIZATION_LATENCY_IN_SINK 0x2009 /* edp 1.4 */
+ # define DP_MAX_RESYNC_FRAME_COUNT_MASK (0xf << 0)
+ # define DP_MAX_RESYNC_FRAME_COUNT_SHIFT 0
+ # define DP_LAST_ACTUAL_SYNCHRONIZATION_LATENCY_MASK (0xf << 4)
+ # define DP_LAST_ACTUAL_SYNCHRONIZATION_LATENCY_SHIFT 4
+
+ #define DP_LAST_RECEIVED_PSR_SDP 0x200a /* eDP 1.2 */
+ # define DP_PSR_STATE_BIT (1 << 0) /* eDP 1.2 */
+ # define DP_UPDATE_RFB_BIT (1 << 1) /* eDP 1.2 */
+ # define DP_CRC_VALID_BIT (1 << 2) /* eDP 1.2 */
+ # define DP_SU_VALID (1 << 3) /* eDP 1.4 */
+ # define DP_FIRST_SCAN_LINE_SU_REGION (1 << 4) /* eDP 1.4 */
+ # define DP_LAST_SCAN_LINE_SU_REGION (1 << 5) /* eDP 1.4 */
+ # define DP_Y_COORDINATE_VALID (1 << 6) /* eDP 1.4a */
+
+ #define DP_RECEIVER_ALPM_STATUS 0x200b /* eDP 1.4 */
+ # define DP_ALPM_LOCK_TIMEOUT_ERROR (1 << 0)
+
+ #define DP_LANE0_1_STATUS_ESI 0x200c /* status same as 0x202 */
+ #define DP_LANE2_3_STATUS_ESI 0x200d /* status same as 0x203 */
+ #define DP_LANE_ALIGN_STATUS_UPDATED_ESI 0x200e /* status same as 0x204 */
+ #define DP_SINK_STATUS_ESI 0x200f /* status same as 0x205 */
+
+ /* Extended Receiver Capability: See DP_DPCD_REV for definitions */
+ #define DP_DP13_DPCD_REV 0x2200
+
+ #define DP_DPRX_FEATURE_ENUMERATION_LIST 0x2210 /* DP 1.3 */
+ # define DP_GTC_CAP (1 << 0) /* DP 1.3 */
+ # define DP_SST_SPLIT_SDP_CAP (1 << 1) /* DP 1.4 */
+ # define DP_AV_SYNC_CAP (1 << 2) /* DP 1.3 */
+ # define DP_VSC_SDP_EXT_FOR_COLORIMETRY_SUPPORTED (1 << 3) /* DP 1.3 */
+ # define DP_VSC_EXT_VESA_SDP_SUPPORTED (1 << 4) /* DP 1.4 */
+ # define DP_VSC_EXT_VESA_SDP_CHAINING_SUPPORTED (1 << 5) /* DP 1.4 */
+ # define DP_VSC_EXT_CEA_SDP_SUPPORTED (1 << 6) /* DP 1.4 */
+ # define DP_VSC_EXT_CEA_SDP_CHAINING_SUPPORTED (1 << 7) /* DP 1.4 */
+
+ #define DP_128B132B_SUPPORTED_LINK_RATES 0x2215 /* 2.0 */
+ # define DP_UHBR10 (1 << 0)
+ # define DP_UHBR20 (1 << 1)
+ # define DP_UHBR13_5 (1 << 2)
+
+ #define DP_128B132B_TRAINING_AUX_RD_INTERVAL 0x2216 /* 2.0 */
+ # define DP_128B132B_TRAINING_AUX_RD_INTERVAL_MASK 0x7f
+ # define DP_128B132B_TRAINING_AUX_RD_INTERVAL_400_US 0x00
+ # define DP_128B132B_TRAINING_AUX_RD_INTERVAL_4_MS 0x01
+ # define DP_128B132B_TRAINING_AUX_RD_INTERVAL_8_MS 0x02
+ # define DP_128B132B_TRAINING_AUX_RD_INTERVAL_12_MS 0x03
+ # define DP_128B132B_TRAINING_AUX_RD_INTERVAL_16_MS 0x04
+ # define DP_128B132B_TRAINING_AUX_RD_INTERVAL_32_MS 0x05
+ # define DP_128B132B_TRAINING_AUX_RD_INTERVAL_64_MS 0x06
+
+ #define DP_TEST_264BIT_CUSTOM_PATTERN_7_0 0x2230
+ #define DP_TEST_264BIT_CUSTOM_PATTERN_263_256 0x2250
+
+ /* DSC Extended Capability Branch Total DSC Resources */
+ #define DP_DSC_SUPPORT_AND_DSC_DECODER_COUNT 0x2260 /* 2.0 */
+ # define DP_DSC_DECODER_COUNT_MASK (0b111 << 5)
+ # define DP_DSC_DECODER_COUNT_SHIFT 5
+ #define DP_DSC_MAX_SLICE_COUNT_AND_AGGREGATION_0 0x2270 /* 2.0 */
+ # define DP_DSC_DECODER_0_MAXIMUM_SLICE_COUNT_MASK (1 << 0)
+ # define DP_DSC_DECODER_0_AGGREGATION_SUPPORT_MASK (0b111 << 1)
+ # define DP_DSC_DECODER_0_AGGREGATION_SUPPORT_SHIFT 1
+
+ /* Protocol Converter Extension */
+ /* HDMI CEC tunneling over AUX DP 1.3 section 5.3.3.3.1 DPCD 1.4+ */
+ #define DP_CEC_TUNNELING_CAPABILITY 0x3000
+ # define DP_CEC_TUNNELING_CAPABLE (1 << 0)
+ # define DP_CEC_SNOOPING_CAPABLE (1 << 1)
+ # define DP_CEC_MULTIPLE_LA_CAPABLE (1 << 2)
+
+ #define DP_CEC_TUNNELING_CONTROL 0x3001
+ # define DP_CEC_TUNNELING_ENABLE (1 << 0)
+ # define DP_CEC_SNOOPING_ENABLE (1 << 1)
+
+ #define DP_CEC_RX_MESSAGE_INFO 0x3002
+ # define DP_CEC_RX_MESSAGE_LEN_MASK (0xf << 0)
+ # define DP_CEC_RX_MESSAGE_LEN_SHIFT 0
+ # define DP_CEC_RX_MESSAGE_HPD_STATE (1 << 4)
+ # define DP_CEC_RX_MESSAGE_HPD_LOST (1 << 5)
+ # define DP_CEC_RX_MESSAGE_ACKED (1 << 6)
+ # define DP_CEC_RX_MESSAGE_ENDED (1 << 7)
+
+ #define DP_CEC_TX_MESSAGE_INFO 0x3003
+ # define DP_CEC_TX_MESSAGE_LEN_MASK (0xf << 0)
+ # define DP_CEC_TX_MESSAGE_LEN_SHIFT 0
+ # define DP_CEC_TX_RETRY_COUNT_MASK (0x7 << 4)
+ # define DP_CEC_TX_RETRY_COUNT_SHIFT 4
+ # define DP_CEC_TX_MESSAGE_SEND (1 << 7)
+
+ #define DP_CEC_TUNNELING_IRQ_FLAGS 0x3004
+ # define DP_CEC_RX_MESSAGE_INFO_VALID (1 << 0)
+ # define DP_CEC_RX_MESSAGE_OVERFLOW (1 << 1)
+ # define DP_CEC_TX_MESSAGE_SENT (1 << 4)
+ # define DP_CEC_TX_LINE_ERROR (1 << 5)
+ # define DP_CEC_TX_ADDRESS_NACK_ERROR (1 << 6)
+ # define DP_CEC_TX_DATA_NACK_ERROR (1 << 7)
+
+ #define DP_CEC_LOGICAL_ADDRESS_MASK 0x300E /* 0x300F word */
+ # define DP_CEC_LOGICAL_ADDRESS_0 (1 << 0)
+ # define DP_CEC_LOGICAL_ADDRESS_1 (1 << 1)
+ # define DP_CEC_LOGICAL_ADDRESS_2 (1 << 2)
+ # define DP_CEC_LOGICAL_ADDRESS_3 (1 << 3)
+ # define DP_CEC_LOGICAL_ADDRESS_4 (1 << 4)
+ # define DP_CEC_LOGICAL_ADDRESS_5 (1 << 5)
+ # define DP_CEC_LOGICAL_ADDRESS_6 (1 << 6)
+ # define DP_CEC_LOGICAL_ADDRESS_7 (1 << 7)
+ #define DP_CEC_LOGICAL_ADDRESS_MASK_2 0x300F /* 0x300E word */
+ # define DP_CEC_LOGICAL_ADDRESS_8 (1 << 0)
+ # define DP_CEC_LOGICAL_ADDRESS_9 (1 << 1)
+ # define DP_CEC_LOGICAL_ADDRESS_10 (1 << 2)
+ # define DP_CEC_LOGICAL_ADDRESS_11 (1 << 3)
+ # define DP_CEC_LOGICAL_ADDRESS_12 (1 << 4)
+ # define DP_CEC_LOGICAL_ADDRESS_13 (1 << 5)
+ # define DP_CEC_LOGICAL_ADDRESS_14 (1 << 6)
+ # define DP_CEC_LOGICAL_ADDRESS_15 (1 << 7)
+
+ #define DP_CEC_RX_MESSAGE_BUFFER 0x3010
+ #define DP_CEC_TX_MESSAGE_BUFFER 0x3020
+ #define DP_CEC_MESSAGE_BUFFER_LENGTH 0x10
+
+ /* PCON CONFIGURE-1 FRL FOR HDMI SINK */
+ #define DP_PCON_HDMI_LINK_CONFIG_1 0x305A
+ # define DP_PCON_ENABLE_MAX_FRL_BW (7 << 0)
+ # define DP_PCON_ENABLE_MAX_BW_0GBPS 0
+ # define DP_PCON_ENABLE_MAX_BW_9GBPS 1
+ # define DP_PCON_ENABLE_MAX_BW_18GBPS 2
+ # define DP_PCON_ENABLE_MAX_BW_24GBPS 3
+ # define DP_PCON_ENABLE_MAX_BW_32GBPS 4
+ # define DP_PCON_ENABLE_MAX_BW_40GBPS 5
+ # define DP_PCON_ENABLE_MAX_BW_48GBPS 6
+ # define DP_PCON_ENABLE_SOURCE_CTL_MODE (1 << 3)
+ # define DP_PCON_ENABLE_CONCURRENT_LINK (1 << 4)
+ # define DP_PCON_ENABLE_SEQUENTIAL_LINK (0 << 4)
+ # define DP_PCON_ENABLE_LINK_FRL_MODE (1 << 5)
+ # define DP_PCON_ENABLE_HPD_READY (1 << 6)
+ # define DP_PCON_ENABLE_HDMI_LINK (1 << 7)
+
+ /* PCON CONFIGURE-2 FRL FOR HDMI SINK */
+ #define DP_PCON_HDMI_LINK_CONFIG_2 0x305B
+ # define DP_PCON_MAX_LINK_BW_MASK (0x3F << 0)
+ # define DP_PCON_FRL_BW_MASK_9GBPS (1 << 0)
+ # define DP_PCON_FRL_BW_MASK_18GBPS (1 << 1)
+ # define DP_PCON_FRL_BW_MASK_24GBPS (1 << 2)
+ # define DP_PCON_FRL_BW_MASK_32GBPS (1 << 3)
+ # define DP_PCON_FRL_BW_MASK_40GBPS (1 << 4)
+ # define DP_PCON_FRL_BW_MASK_48GBPS (1 << 5)
+ # define DP_PCON_FRL_LINK_TRAIN_EXTENDED (1 << 6)
+ # define DP_PCON_FRL_LINK_TRAIN_NORMAL (0 << 6)
+
+ /* PCON HDMI LINK STATUS */
+ #define DP_PCON_HDMI_TX_LINK_STATUS 0x303B
+ # define DP_PCON_HDMI_TX_LINK_ACTIVE (1 << 0)
+ # define DP_PCON_FRL_READY (1 << 1)
+
+ /* PCON HDMI POST FRL STATUS */
+ #define DP_PCON_HDMI_POST_FRL_STATUS 0x3036
+ # define DP_PCON_HDMI_LINK_MODE (1 << 0)
+ # define DP_PCON_HDMI_MODE_TMDS 0
+ # define DP_PCON_HDMI_MODE_FRL 1
+ # define DP_PCON_HDMI_FRL_TRAINED_BW (0x3F << 1)
+ # define DP_PCON_FRL_TRAINED_BW_9GBPS (1 << 1)
+ # define DP_PCON_FRL_TRAINED_BW_18GBPS (1 << 2)
+ # define DP_PCON_FRL_TRAINED_BW_24GBPS (1 << 3)
+ # define DP_PCON_FRL_TRAINED_BW_32GBPS (1 << 4)
+ # define DP_PCON_FRL_TRAINED_BW_40GBPS (1 << 5)
+ # define DP_PCON_FRL_TRAINED_BW_48GBPS (1 << 6)
+
+ #define DP_PROTOCOL_CONVERTER_CONTROL_0 0x3050 /* DP 1.3 */
+ # define DP_HDMI_DVI_OUTPUT_CONFIG (1 << 0) /* DP 1.3 */
+ #define DP_PROTOCOL_CONVERTER_CONTROL_1 0x3051 /* DP 1.3 */
+ # define DP_CONVERSION_TO_YCBCR420_ENABLE (1 << 0) /* DP 1.3 */
+ # define DP_HDMI_EDID_PROCESSING_DISABLE (1 << 1) /* DP 1.4 */
+ # define DP_HDMI_AUTONOMOUS_SCRAMBLING_DISABLE (1 << 2) /* DP 1.4 */
+ # define DP_HDMI_FORCE_SCRAMBLING (1 << 3) /* DP 1.4 */
+ #define DP_PROTOCOL_CONVERTER_CONTROL_2 0x3052 /* DP 1.3 */
+ # define DP_CONVERSION_TO_YCBCR422_ENABLE (1 << 0) /* DP 1.3 */
+ # define DP_PCON_ENABLE_DSC_ENCODER (1 << 1)
+ # define DP_PCON_ENCODER_PPS_OVERRIDE_MASK (0x3 << 2)
+ # define DP_PCON_ENC_PPS_OVERRIDE_DISABLED 0
+ # define DP_PCON_ENC_PPS_OVERRIDE_EN_PARAMS 1
+ # define DP_PCON_ENC_PPS_OVERRIDE_EN_BUFFER 2
+ # define DP_CONVERSION_RGB_YCBCR_MASK (7 << 4)
+ # define DP_CONVERSION_BT601_RGB_YCBCR_ENABLE (1 << 4)
+ # define DP_CONVERSION_BT709_RGB_YCBCR_ENABLE (1 << 5)
+ # define DP_CONVERSION_BT2020_RGB_YCBCR_ENABLE (1 << 6)
+
+ /* PCON Downstream HDMI ERROR Status per Lane */
+ #define DP_PCON_HDMI_ERROR_STATUS_LN0 0x3037
+ #define DP_PCON_HDMI_ERROR_STATUS_LN1 0x3038
+ #define DP_PCON_HDMI_ERROR_STATUS_LN2 0x3039
+ #define DP_PCON_HDMI_ERROR_STATUS_LN3 0x303A
+ # define DP_PCON_HDMI_ERROR_COUNT_MASK (0x7 << 0)
+ # define DP_PCON_HDMI_ERROR_COUNT_THREE_PLUS (1 << 0)
+ # define DP_PCON_HDMI_ERROR_COUNT_TEN_PLUS (1 << 1)
+ # define DP_PCON_HDMI_ERROR_COUNT_HUNDRED_PLUS (1 << 2)
+
+ /* PCON HDMI CONFIG PPS Override Buffer
+ * Valid Offsets to be added to Base : 0-127
+ */
+ #define DP_PCON_HDMI_PPS_OVERRIDE_BASE 0x3100
+
+ /* PCON HDMI CONFIG PPS Override Parameter: Slice height
+ * Offset-0 8LSBs of the Slice height.
+ * Offset-1 8MSBs of the Slice height.
+ */
+ #define DP_PCON_HDMI_PPS_OVRD_SLICE_HEIGHT 0x3180
+
+ /* PCON HDMI CONFIG PPS Override Parameter: Slice width
+ * Offset-0 8LSBs of the Slice width.
+ * Offset-1 8MSBs of the Slice width.
+ */
+ #define DP_PCON_HDMI_PPS_OVRD_SLICE_WIDTH 0x3182
+
+ /* PCON HDMI CONFIG PPS Override Parameter: bits_per_pixel
+ * Offset-0 8LSBs of the bits_per_pixel.
+ * Offset-1 2MSBs of the bits_per_pixel.
+ */
+ #define DP_PCON_HDMI_PPS_OVRD_BPP 0x3184
+
+ /* HDCP 1.3 and HDCP 2.2 */
+ #define DP_AUX_HDCP_BKSV 0x68000
+ #define DP_AUX_HDCP_RI_PRIME 0x68005
+ #define DP_AUX_HDCP_AKSV 0x68007
+ #define DP_AUX_HDCP_AN 0x6800C
+ #define DP_AUX_HDCP_V_PRIME(h) (0x68014 + h * 4)
+ #define DP_AUX_HDCP_BCAPS 0x68028
+ # define DP_BCAPS_REPEATER_PRESENT BIT(1)
+ # define DP_BCAPS_HDCP_CAPABLE BIT(0)
+ #define DP_AUX_HDCP_BSTATUS 0x68029
+ # define DP_BSTATUS_REAUTH_REQ BIT(3)
+ # define DP_BSTATUS_LINK_FAILURE BIT(2)
+ # define DP_BSTATUS_R0_PRIME_READY BIT(1)
+ # define DP_BSTATUS_READY BIT(0)
+ #define DP_AUX_HDCP_BINFO 0x6802A
+ #define DP_AUX_HDCP_KSV_FIFO 0x6802C
+ #define DP_AUX_HDCP_AINFO 0x6803B
+
+ /* DP HDCP2.2 parameter offsets in DPCD address space */
+ #define DP_HDCP_2_2_REG_RTX_OFFSET 0x69000
+ #define DP_HDCP_2_2_REG_TXCAPS_OFFSET 0x69008
+ #define DP_HDCP_2_2_REG_CERT_RX_OFFSET 0x6900B
+ #define DP_HDCP_2_2_REG_RRX_OFFSET 0x69215
+ #define DP_HDCP_2_2_REG_RX_CAPS_OFFSET 0x6921D
+ #define DP_HDCP_2_2_REG_EKPUB_KM_OFFSET 0x69220
+ #define DP_HDCP_2_2_REG_EKH_KM_WR_OFFSET 0x692A0
+ #define DP_HDCP_2_2_REG_M_OFFSET 0x692B0
+ #define DP_HDCP_2_2_REG_HPRIME_OFFSET 0x692C0
+ #define DP_HDCP_2_2_REG_EKH_KM_RD_OFFSET 0x692E0
+ #define DP_HDCP_2_2_REG_RN_OFFSET 0x692F0
+ #define DP_HDCP_2_2_REG_LPRIME_OFFSET 0x692F8
+ #define DP_HDCP_2_2_REG_EDKEY_KS_OFFSET 0x69318
+ #define DP_HDCP_2_2_REG_RIV_OFFSET 0x69328
+ #define DP_HDCP_2_2_REG_RXINFO_OFFSET 0x69330
+ #define DP_HDCP_2_2_REG_SEQ_NUM_V_OFFSET 0x69332
+ #define DP_HDCP_2_2_REG_VPRIME_OFFSET 0x69335
+ #define DP_HDCP_2_2_REG_RECV_ID_LIST_OFFSET 0x69345
+ #define DP_HDCP_2_2_REG_V_OFFSET 0x693E0
+ #define DP_HDCP_2_2_REG_SEQ_NUM_M_OFFSET 0x693F0
+ #define DP_HDCP_2_2_REG_K_OFFSET 0x693F3
+ #define DP_HDCP_2_2_REG_STREAM_ID_TYPE_OFFSET 0x693F5
+ #define DP_HDCP_2_2_REG_MPRIME_OFFSET 0x69473
+ #define DP_HDCP_2_2_REG_RXSTATUS_OFFSET 0x69493
+ #define DP_HDCP_2_2_REG_STREAM_TYPE_OFFSET 0x69494
+ #define DP_HDCP_2_2_REG_DBG_OFFSET 0x69518
+
+ /* LTTPR: Link Training (LT)-tunable PHY Repeaters */
+ #define DP_LT_TUNABLE_PHY_REPEATER_FIELD_DATA_STRUCTURE_REV 0xf0000 /* 1.3 */
+ #define DP_MAX_LINK_RATE_PHY_REPEATER 0xf0001 /* 1.4a */
+ #define DP_PHY_REPEATER_CNT 0xf0002 /* 1.3 */
+ #define DP_PHY_REPEATER_MODE 0xf0003 /* 1.3 */
+ #define DP_MAX_LANE_COUNT_PHY_REPEATER 0xf0004 /* 1.4a */
+ #define DP_Repeater_FEC_CAPABILITY 0xf0004 /* 1.4 */
+ #define DP_PHY_REPEATER_EXTENDED_WAIT_TIMEOUT 0xf0005 /* 1.4a */
+ #define DP_MAIN_LINK_CHANNEL_CODING_PHY_REPEATER 0xf0006 /* 2.0 */
+ # define DP_PHY_REPEATER_128B132B_SUPPORTED (1 << 0)
+ /* See DP_128B132B_SUPPORTED_LINK_RATES for values */
+ #define DP_PHY_REPEATER_128B132B_RATES 0xf0007 /* 2.0 */
+
+ enum drm_dp_phy {
+ DP_PHY_DPRX,
+
+ DP_PHY_LTTPR1,
+ DP_PHY_LTTPR2,
+ DP_PHY_LTTPR3,
+ DP_PHY_LTTPR4,
+ DP_PHY_LTTPR5,
+ DP_PHY_LTTPR6,
+ DP_PHY_LTTPR7,
+ DP_PHY_LTTPR8,
+
+ DP_MAX_LTTPR_COUNT = DP_PHY_LTTPR8,
+ };
+
+ #define DP_PHY_LTTPR(i) (DP_PHY_LTTPR1 + (i))
+
+ #define __DP_LTTPR1_BASE 0xf0010 /* 1.3 */
+ #define __DP_LTTPR2_BASE 0xf0060 /* 1.3 */
+ #define DP_LTTPR_BASE(dp_phy) \
+ (__DP_LTTPR1_BASE + (__DP_LTTPR2_BASE - __DP_LTTPR1_BASE) * \
+ ((dp_phy) - DP_PHY_LTTPR1))
+
+ #define DP_LTTPR_REG(dp_phy, lttpr1_reg) \
+ (DP_LTTPR_BASE(dp_phy) - DP_LTTPR_BASE(DP_PHY_LTTPR1) + (lttpr1_reg))
+
+ #define DP_TRAINING_PATTERN_SET_PHY_REPEATER1 0xf0010 /* 1.3 */
+ #define DP_TRAINING_PATTERN_SET_PHY_REPEATER(dp_phy) \
+ DP_LTTPR_REG(dp_phy, DP_TRAINING_PATTERN_SET_PHY_REPEATER1)
+
+ #define DP_TRAINING_LANE0_SET_PHY_REPEATER1 0xf0011 /* 1.3 */
+ #define DP_TRAINING_LANE0_SET_PHY_REPEATER(dp_phy) \
+ DP_LTTPR_REG(dp_phy, DP_TRAINING_LANE0_SET_PHY_REPEATER1)
+
+ #define DP_TRAINING_LANE1_SET_PHY_REPEATER1 0xf0012 /* 1.3 */
+ #define DP_TRAINING_LANE2_SET_PHY_REPEATER1 0xf0013 /* 1.3 */
+ #define DP_TRAINING_LANE3_SET_PHY_REPEATER1 0xf0014 /* 1.3 */
+ #define DP_TRAINING_AUX_RD_INTERVAL_PHY_REPEATER1 0xf0020 /* 1.4a */
+ #define DP_TRAINING_AUX_RD_INTERVAL_PHY_REPEATER(dp_phy) \
+ DP_LTTPR_REG(dp_phy, DP_TRAINING_AUX_RD_INTERVAL_PHY_REPEATER1)
+
+ #define DP_TRANSMITTER_CAPABILITY_PHY_REPEATER1 0xf0021 /* 1.4a */
+ # define DP_VOLTAGE_SWING_LEVEL_3_SUPPORTED BIT(0)
+ # define DP_PRE_EMPHASIS_LEVEL_3_SUPPORTED BIT(1)
+
+ #define DP_128B132B_TRAINING_AUX_RD_INTERVAL_PHY_REPEATER1 0xf0022 /* 2.0 */
+ #define DP_128B132B_TRAINING_AUX_RD_INTERVAL_PHY_REPEATER(dp_phy) \
+ DP_LTTPR_REG(dp_phy, DP_128B132B_TRAINING_AUX_RD_INTERVAL_PHY_REPEATER1)
+ /* see DP_128B132B_TRAINING_AUX_RD_INTERVAL for values */
+
+ #define DP_LANE0_1_STATUS_PHY_REPEATER1 0xf0030 /* 1.3 */
+ #define DP_LANE0_1_STATUS_PHY_REPEATER(dp_phy) \
+ DP_LTTPR_REG(dp_phy, DP_LANE0_1_STATUS_PHY_REPEATER1)
+
+ #define DP_LANE2_3_STATUS_PHY_REPEATER1 0xf0031 /* 1.3 */
+
+ #define DP_LANE_ALIGN_STATUS_UPDATED_PHY_REPEATER1 0xf0032 /* 1.3 */
+ #define DP_ADJUST_REQUEST_LANE0_1_PHY_REPEATER1 0xf0033 /* 1.3 */
+ #define DP_ADJUST_REQUEST_LANE2_3_PHY_REPEATER1 0xf0034 /* 1.3 */
+ #define DP_SYMBOL_ERROR_COUNT_LANE0_PHY_REPEATER1 0xf0035 /* 1.3 */
+ #define DP_SYMBOL_ERROR_COUNT_LANE1_PHY_REPEATER1 0xf0037 /* 1.3 */
+ #define DP_SYMBOL_ERROR_COUNT_LANE2_PHY_REPEATER1 0xf0039 /* 1.3 */
+ #define DP_SYMBOL_ERROR_COUNT_LANE3_PHY_REPEATER1 0xf003b /* 1.3 */
+
+ #define __DP_FEC1_BASE 0xf0290 /* 1.4 */
+ #define __DP_FEC2_BASE 0xf0298 /* 1.4 */
+ #define DP_FEC_BASE(dp_phy) \
+ (__DP_FEC1_BASE + ((__DP_FEC2_BASE - __DP_FEC1_BASE) * \
+ ((dp_phy) - DP_PHY_LTTPR1)))
+
+ #define DP_FEC_REG(dp_phy, fec1_reg) \
+ (DP_FEC_BASE(dp_phy) - DP_FEC_BASE(DP_PHY_LTTPR1) + fec1_reg)
+
+ #define DP_FEC_STATUS_PHY_REPEATER1 0xf0290 /* 1.4 */
+ #define DP_FEC_STATUS_PHY_REPEATER(dp_phy) \
+ DP_FEC_REG(dp_phy, DP_FEC_STATUS_PHY_REPEATER1)
+
+ #define DP_FEC_ERROR_COUNT_PHY_REPEATER1 0xf0291 /* 1.4 */
+ #define DP_FEC_CAPABILITY_PHY_REPEATER1 0xf0294 /* 1.4a */
+
+ #define DP_LTTPR_MAX_ADD 0xf02ff /* 1.4 */
+
+ #define DP_DPCD_MAX_ADD 0xfffff /* 1.4 */
+
+ /* Repeater modes */
+ #define DP_PHY_REPEATER_MODE_TRANSPARENT 0x55 /* 1.3 */
+ #define DP_PHY_REPEATER_MODE_NON_TRANSPARENT 0xaa /* 1.3 */
+
+ /* DP HDCP message start offsets in DPCD address space */
+ #define DP_HDCP_2_2_AKE_INIT_OFFSET DP_HDCP_2_2_REG_RTX_OFFSET
+ #define DP_HDCP_2_2_AKE_SEND_CERT_OFFSET DP_HDCP_2_2_REG_CERT_RX_OFFSET
+ #define DP_HDCP_2_2_AKE_NO_STORED_KM_OFFSET DP_HDCP_2_2_REG_EKPUB_KM_OFFSET
+ #define DP_HDCP_2_2_AKE_STORED_KM_OFFSET DP_HDCP_2_2_REG_EKH_KM_WR_OFFSET
+ #define DP_HDCP_2_2_AKE_SEND_HPRIME_OFFSET DP_HDCP_2_2_REG_HPRIME_OFFSET
+ #define DP_HDCP_2_2_AKE_SEND_PAIRING_INFO_OFFSET \
+ DP_HDCP_2_2_REG_EKH_KM_RD_OFFSET
+ #define DP_HDCP_2_2_LC_INIT_OFFSET DP_HDCP_2_2_REG_RN_OFFSET
+ #define DP_HDCP_2_2_LC_SEND_LPRIME_OFFSET DP_HDCP_2_2_REG_LPRIME_OFFSET
+ #define DP_HDCP_2_2_SKE_SEND_EKS_OFFSET DP_HDCP_2_2_REG_EDKEY_KS_OFFSET
+ #define DP_HDCP_2_2_REP_SEND_RECVID_LIST_OFFSET DP_HDCP_2_2_REG_RXINFO_OFFSET
+ #define DP_HDCP_2_2_REP_SEND_ACK_OFFSET DP_HDCP_2_2_REG_V_OFFSET
+ #define DP_HDCP_2_2_REP_STREAM_MANAGE_OFFSET DP_HDCP_2_2_REG_SEQ_NUM_M_OFFSET
+ #define DP_HDCP_2_2_REP_STREAM_READY_OFFSET DP_HDCP_2_2_REG_MPRIME_OFFSET
+
+ #define HDCP_2_2_DP_RXSTATUS_LEN 1
+ #define HDCP_2_2_DP_RXSTATUS_READY(x) ((x) & BIT(0))
+ #define HDCP_2_2_DP_RXSTATUS_H_PRIME(x) ((x) & BIT(1))
+ #define HDCP_2_2_DP_RXSTATUS_PAIRING(x) ((x) & BIT(2))
+ #define HDCP_2_2_DP_RXSTATUS_REAUTH_REQ(x) ((x) & BIT(3))
+ #define HDCP_2_2_DP_RXSTATUS_LINK_FAILED(x) ((x) & BIT(4))
+
+ /* DP 1.2 Sideband message defines */
+ /* peer device type - DP 1.2a Table 2-92 */
+ #define DP_PEER_DEVICE_NONE 0x0
+ #define DP_PEER_DEVICE_SOURCE_OR_SST 0x1
+ #define DP_PEER_DEVICE_MST_BRANCHING 0x2
+ #define DP_PEER_DEVICE_SST_SINK 0x3
+ #define DP_PEER_DEVICE_DP_LEGACY_CONV 0x4
+
+ /* DP 1.2 MST sideband request names DP 1.2a Table 2-80 */
+ #define DP_GET_MSG_TRANSACTION_VERSION 0x00 /* DP 1.3 */
+ #define DP_LINK_ADDRESS 0x01
+ #define DP_CONNECTION_STATUS_NOTIFY 0x02
+ #define DP_ENUM_PATH_RESOURCES 0x10
+ #define DP_ALLOCATE_PAYLOAD 0x11
+ #define DP_QUERY_PAYLOAD 0x12
+ #define DP_RESOURCE_STATUS_NOTIFY 0x13
+ #define DP_CLEAR_PAYLOAD_ID_TABLE 0x14
+ #define DP_REMOTE_DPCD_READ 0x20
+ #define DP_REMOTE_DPCD_WRITE 0x21
+ #define DP_REMOTE_I2C_READ 0x22
+ #define DP_REMOTE_I2C_WRITE 0x23
+ #define DP_POWER_UP_PHY 0x24
+ #define DP_POWER_DOWN_PHY 0x25
+ #define DP_SINK_EVENT_NOTIFY 0x30
+ #define DP_QUERY_STREAM_ENC_STATUS 0x38
+ #define DP_QUERY_STREAM_ENC_STATUS_STATE_NO_EXIST 0
+ #define DP_QUERY_STREAM_ENC_STATUS_STATE_INACTIVE 1
+ #define DP_QUERY_STREAM_ENC_STATUS_STATE_ACTIVE 2
+
+ /* DP 1.2 MST sideband reply types */
+ #define DP_SIDEBAND_REPLY_ACK 0x00
+ #define DP_SIDEBAND_REPLY_NAK 0x01
+
+ /* DP 1.2 MST sideband nak reasons - table 2.84 */
+ #define DP_NAK_WRITE_FAILURE 0x01
+ #define DP_NAK_INVALID_READ 0x02
+ #define DP_NAK_CRC_FAILURE 0x03
+ #define DP_NAK_BAD_PARAM 0x04
+ #define DP_NAK_DEFER 0x05
+ #define DP_NAK_LINK_FAILURE 0x06
+ #define DP_NAK_NO_RESOURCES 0x07
+ #define DP_NAK_DPCD_FAIL 0x08
+ #define DP_NAK_I2C_NAK 0x09
+ #define DP_NAK_ALLOCATE_FAIL 0x0a
+
+ #define MODE_I2C_START 1
+ #define MODE_I2C_WRITE 2
+ #define MODE_I2C_READ 4
+ #define MODE_I2C_STOP 8
+
+ /* DP 1.2 MST PORTs - Section 2.5.1 v1.2a spec */
+ #define DP_MST_PHYSICAL_PORT_0 0
+ #define DP_MST_LOGICAL_PORT_0 8
+
+ #define DP_LINK_CONSTANT_N_VALUE 0x8000
+ #define DP_LINK_STATUS_SIZE 6
+ bool drm_dp_channel_eq_ok(const u8 link_status[DP_LINK_STATUS_SIZE],
+ int lane_count);
+ bool drm_dp_clock_recovery_ok(const u8 link_status[DP_LINK_STATUS_SIZE],
+ int lane_count);
+ u8 drm_dp_get_adjust_request_voltage(const u8 link_status[DP_LINK_STATUS_SIZE],
+ int lane);
+ u8 drm_dp_get_adjust_request_pre_emphasis(const u8 link_status[DP_LINK_STATUS_SIZE],
+ int lane);
+ u8 drm_dp_get_adjust_tx_ffe_preset(const u8 link_status[DP_LINK_STATUS_SIZE],
+ int lane);
+ u8 drm_dp_get_adjust_request_post_cursor(const u8 link_status[DP_LINK_STATUS_SIZE],
+ unsigned int lane);
+
+ #define DP_BRANCH_OUI_HEADER_SIZE 0xc
+ #define DP_RECEIVER_CAP_SIZE 0xf
+ #define DP_DSC_RECEIVER_CAP_SIZE 0xf
+ #define EDP_PSR_RECEIVER_CAP_SIZE 2
+ #define EDP_DISPLAY_CTL_CAP_SIZE 3
+ #define DP_LTTPR_COMMON_CAP_SIZE 8
+ #define DP_LTTPR_PHY_CAP_SIZE 3
+
+ int drm_dp_read_clock_recovery_delay(struct drm_dp_aux *aux, const u8 dpcd[DP_RECEIVER_CAP_SIZE],
+ enum drm_dp_phy dp_phy, bool uhbr);
+ int drm_dp_read_channel_eq_delay(struct drm_dp_aux *aux, const u8 dpcd[DP_RECEIVER_CAP_SIZE],
+ enum drm_dp_phy dp_phy, bool uhbr);
+
+ void drm_dp_link_train_clock_recovery_delay(const struct drm_dp_aux *aux,
+ const u8 dpcd[DP_RECEIVER_CAP_SIZE]);
+ void drm_dp_lttpr_link_train_clock_recovery_delay(void);
+ void drm_dp_link_train_channel_eq_delay(const struct drm_dp_aux *aux,
+ const u8 dpcd[DP_RECEIVER_CAP_SIZE]);
+ void drm_dp_lttpr_link_train_channel_eq_delay(const struct drm_dp_aux *aux,
+ const u8 caps[DP_LTTPR_PHY_CAP_SIZE]);
+
+ u8 drm_dp_link_rate_to_bw_code(int link_rate);
+ int drm_dp_bw_code_to_link_rate(u8 link_bw);
+
+ #define DP_SDP_AUDIO_TIMESTAMP 0x01
+ #define DP_SDP_AUDIO_STREAM 0x02
+ #define DP_SDP_EXTENSION 0x04 /* DP 1.1 */
+ #define DP_SDP_AUDIO_COPYMANAGEMENT 0x05 /* DP 1.2 */
+ #define DP_SDP_ISRC 0x06 /* DP 1.2 */
+ #define DP_SDP_VSC 0x07 /* DP 1.2 */
+ #define DP_SDP_CAMERA_GENERIC(i) (0x08 + (i)) /* 0-7, DP 1.3 */
+ #define DP_SDP_PPS 0x10 /* DP 1.4 */
+ #define DP_SDP_VSC_EXT_VESA 0x20 /* DP 1.4 */
+ #define DP_SDP_VSC_EXT_CEA 0x21 /* DP 1.4 */
+ /* 0x80+ CEA-861 infoframe types */
+
+ /**
+ * struct dp_sdp_header - DP secondary data packet header
+ * @HB0: Secondary Data Packet ID
+ * @HB1: Secondary Data Packet Type
+ * @HB2: Secondary Data Packet Specific header, Byte 0
+ * @HB3: Secondary Data packet Specific header, Byte 1
+ */
+ struct dp_sdp_header {
+ u8 HB0;
+ u8 HB1;
+ u8 HB2;
+ u8 HB3;
+ } __packed;
+
+ #define EDP_SDP_HEADER_REVISION_MASK 0x1F
+ #define EDP_SDP_HEADER_VALID_PAYLOAD_BYTES 0x1F
+ #define DP_SDP_PPS_HEADER_PAYLOAD_BYTES_MINUS_1 0x7F
+
+ /**
+ * struct dp_sdp - DP secondary data packet
+ * @sdp_header: DP secondary data packet header
+ * @db: DP secondaray data packet data blocks
+ * VSC SDP Payload for PSR
+ * db[0]: Stereo Interface
+ * db[1]: 0 - PSR State; 1 - Update RFB; 2 - CRC Valid
+ * db[2]: CRC value bits 7:0 of the R or Cr component
+ * db[3]: CRC value bits 15:8 of the R or Cr component
+ * db[4]: CRC value bits 7:0 of the G or Y component
+ * db[5]: CRC value bits 15:8 of the G or Y component
+ * db[6]: CRC value bits 7:0 of the B or Cb component
+ * db[7]: CRC value bits 15:8 of the B or Cb component
+ * db[8] - db[31]: Reserved
+ * VSC SDP Payload for Pixel Encoding/Colorimetry Format
+ * db[0] - db[15]: Reserved
+ * db[16]: Pixel Encoding and Colorimetry Formats
+ * db[17]: Dynamic Range and Component Bit Depth
+ * db[18]: Content Type
+ * db[19] - db[31]: Reserved
+ */
+ struct dp_sdp {
+ struct dp_sdp_header sdp_header;
+ u8 db[32];
+ } __packed;
+
+ #define EDP_VSC_PSR_STATE_ACTIVE (1<<0)
+ #define EDP_VSC_PSR_UPDATE_RFB (1<<1)
+ #define EDP_VSC_PSR_CRC_VALUES_VALID (1<<2)
+
+ /**
+ * enum dp_pixelformat - drm DP Pixel encoding formats
+ *
+ * This enum is used to indicate DP VSC SDP Pixel encoding formats.
+ * It is based on DP 1.4 spec [Table 2-117: VSC SDP Payload for DB16 through
+ * DB18]
+ *
+ * @DP_PIXELFORMAT_RGB: RGB pixel encoding format
+ * @DP_PIXELFORMAT_YUV444: YCbCr 4:4:4 pixel encoding format
+ * @DP_PIXELFORMAT_YUV422: YCbCr 4:2:2 pixel encoding format
+ * @DP_PIXELFORMAT_YUV420: YCbCr 4:2:0 pixel encoding format
+ * @DP_PIXELFORMAT_Y_ONLY: Y Only pixel encoding format
+ * @DP_PIXELFORMAT_RAW: RAW pixel encoding format
+ * @DP_PIXELFORMAT_RESERVED: Reserved pixel encoding format
+ */
+ enum dp_pixelformat {
+ DP_PIXELFORMAT_RGB = 0,
+ DP_PIXELFORMAT_YUV444 = 0x1,
+ DP_PIXELFORMAT_YUV422 = 0x2,
+ DP_PIXELFORMAT_YUV420 = 0x3,
+ DP_PIXELFORMAT_Y_ONLY = 0x4,
+ DP_PIXELFORMAT_RAW = 0x5,
+ DP_PIXELFORMAT_RESERVED = 0x6,
+ };
+
+ /**
+ * enum dp_colorimetry - drm DP Colorimetry formats
+ *
+ * This enum is used to indicate DP VSC SDP Colorimetry formats.
+ * It is based on DP 1.4 spec [Table 2-117: VSC SDP Payload for DB16 through
+ * DB18] and a name of enum member follows DRM_MODE_COLORIMETRY definition.
+ *
+ * @DP_COLORIMETRY_DEFAULT: sRGB (IEC 61966-2-1) or
+ * ITU-R BT.601 colorimetry format
+ * @DP_COLORIMETRY_RGB_WIDE_FIXED: RGB wide gamut fixed point colorimetry format
+ * @DP_COLORIMETRY_BT709_YCC: ITU-R BT.709 colorimetry format
+ * @DP_COLORIMETRY_RGB_WIDE_FLOAT: RGB wide gamut floating point
+ * (scRGB (IEC 61966-2-2)) colorimetry format
+ * @DP_COLORIMETRY_XVYCC_601: xvYCC601 colorimetry format
+ * @DP_COLORIMETRY_OPRGB: OpRGB colorimetry format
+ * @DP_COLORIMETRY_XVYCC_709: xvYCC709 colorimetry format
+ * @DP_COLORIMETRY_DCI_P3_RGB: DCI-P3 (SMPTE RP 431-2) colorimetry format
+ * @DP_COLORIMETRY_SYCC_601: sYCC601 colorimetry format
+ * @DP_COLORIMETRY_RGB_CUSTOM: RGB Custom Color Profile colorimetry format
+ * @DP_COLORIMETRY_OPYCC_601: opYCC601 colorimetry format
+ * @DP_COLORIMETRY_BT2020_RGB: ITU-R BT.2020 R' G' B' colorimetry format
+ * @DP_COLORIMETRY_BT2020_CYCC: ITU-R BT.2020 Y'c C'bc C'rc colorimetry format
+ * @DP_COLORIMETRY_BT2020_YCC: ITU-R BT.2020 Y' C'b C'r colorimetry format
+ */
+ enum dp_colorimetry {
+ DP_COLORIMETRY_DEFAULT = 0,
+ DP_COLORIMETRY_RGB_WIDE_FIXED = 0x1,
+ DP_COLORIMETRY_BT709_YCC = 0x1,
+ DP_COLORIMETRY_RGB_WIDE_FLOAT = 0x2,
+ DP_COLORIMETRY_XVYCC_601 = 0x2,
+ DP_COLORIMETRY_OPRGB = 0x3,
+ DP_COLORIMETRY_XVYCC_709 = 0x3,
+ DP_COLORIMETRY_DCI_P3_RGB = 0x4,
+ DP_COLORIMETRY_SYCC_601 = 0x4,
+ DP_COLORIMETRY_RGB_CUSTOM = 0x5,
+ DP_COLORIMETRY_OPYCC_601 = 0x5,
+ DP_COLORIMETRY_BT2020_RGB = 0x6,
+ DP_COLORIMETRY_BT2020_CYCC = 0x6,
+ DP_COLORIMETRY_BT2020_YCC = 0x7,
+ };
+
+ /**
+ * enum dp_dynamic_range - drm DP Dynamic Range
+ *
+ * This enum is used to indicate DP VSC SDP Dynamic Range.
+ * It is based on DP 1.4 spec [Table 2-117: VSC SDP Payload for DB16 through
+ * DB18]
+ *
+ * @DP_DYNAMIC_RANGE_VESA: VESA range
+ * @DP_DYNAMIC_RANGE_CTA: CTA range
+ */
+ enum dp_dynamic_range {
+ DP_DYNAMIC_RANGE_VESA = 0,
+ DP_DYNAMIC_RANGE_CTA = 1,
+ };
+
+ /**
+ * enum dp_content_type - drm DP Content Type
+ *
+ * This enum is used to indicate DP VSC SDP Content Types.
+ * It is based on DP 1.4 spec [Table 2-117: VSC SDP Payload for DB16 through
+ * DB18]
+ * CTA-861-G defines content types and expected processing by a sink device
+ *
+ * @DP_CONTENT_TYPE_NOT_DEFINED: Not defined type
+ * @DP_CONTENT_TYPE_GRAPHICS: Graphics type
+ * @DP_CONTENT_TYPE_PHOTO: Photo type
+ * @DP_CONTENT_TYPE_VIDEO: Video type
+ * @DP_CONTENT_TYPE_GAME: Game type
+ */
+ enum dp_content_type {
+ DP_CONTENT_TYPE_NOT_DEFINED = 0x00,
+ DP_CONTENT_TYPE_GRAPHICS = 0x01,
+ DP_CONTENT_TYPE_PHOTO = 0x02,
+ DP_CONTENT_TYPE_VIDEO = 0x03,
+ DP_CONTENT_TYPE_GAME = 0x04,
+ };
+
+ /**
+ * struct drm_dp_vsc_sdp - drm DP VSC SDP
+ *
+ * This structure represents a DP VSC SDP of drm
+ * It is based on DP 1.4 spec [Table 2-116: VSC SDP Header Bytes] and
+ * [Table 2-117: VSC SDP Payload for DB16 through DB18]
+ *
+ * @sdp_type: secondary-data packet type
+ * @revision: revision number
+ * @length: number of valid data bytes
+ * @pixelformat: pixel encoding format
+ * @colorimetry: colorimetry format
+ * @bpc: bit per color
+ * @dynamic_range: dynamic range information
+ * @content_type: CTA-861-G defines content types and expected processing by a sink device
+ */
+ struct drm_dp_vsc_sdp {
+ unsigned char sdp_type;
+ unsigned char revision;
+ unsigned char length;
+ enum dp_pixelformat pixelformat;
+ enum dp_colorimetry colorimetry;
+ int bpc;
+ enum dp_dynamic_range dynamic_range;
+ enum dp_content_type content_type;
+ };
+
+ void drm_dp_vsc_sdp_log(const char *level, struct device *dev,
+ const struct drm_dp_vsc_sdp *vsc);
+
+ int drm_dp_psr_setup_time(const u8 psr_cap[EDP_PSR_RECEIVER_CAP_SIZE]);
+
+ static inline int
+ drm_dp_max_link_rate(const u8 dpcd[DP_RECEIVER_CAP_SIZE])
+ {
+ return drm_dp_bw_code_to_link_rate(dpcd[DP_MAX_LINK_RATE]);
+ }
+
+ static inline u8
+ drm_dp_max_lane_count(const u8 dpcd[DP_RECEIVER_CAP_SIZE])
+ {
+ return dpcd[DP_MAX_LANE_COUNT] & DP_MAX_LANE_COUNT_MASK;
+ }
+
+ static inline bool
+ drm_dp_enhanced_frame_cap(const u8 dpcd[DP_RECEIVER_CAP_SIZE])
+ {
+ return dpcd[DP_DPCD_REV] >= 0x11 &&
+ (dpcd[DP_MAX_LANE_COUNT] & DP_ENHANCED_FRAME_CAP);
+ }
+
+ static inline bool
+ drm_dp_fast_training_cap(const u8 dpcd[DP_RECEIVER_CAP_SIZE])
+ {
+ return dpcd[DP_DPCD_REV] >= 0x11 &&
+ (dpcd[DP_MAX_DOWNSPREAD] & DP_NO_AUX_HANDSHAKE_LINK_TRAINING);
+ }
+
+ static inline bool
+ drm_dp_tps3_supported(const u8 dpcd[DP_RECEIVER_CAP_SIZE])
+ {
+ return dpcd[DP_DPCD_REV] >= 0x12 &&
+ dpcd[DP_MAX_LANE_COUNT] & DP_TPS3_SUPPORTED;
+ }
+
++static inline bool
++drm_dp_max_downspread(const u8 dpcd[DP_RECEIVER_CAP_SIZE])
++{
++ return dpcd[DP_DPCD_REV] >= 0x11 ||
++ dpcd[DP_MAX_DOWNSPREAD] & DP_MAX_DOWNSPREAD_0_5;
++}
++
+ static inline bool
+ drm_dp_tps4_supported(const u8 dpcd[DP_RECEIVER_CAP_SIZE])
+ {
+ return dpcd[DP_DPCD_REV] >= 0x14 &&
+ dpcd[DP_MAX_DOWNSPREAD] & DP_TPS4_SUPPORTED;
+ }
+
+ static inline u8
+ drm_dp_training_pattern_mask(const u8 dpcd[DP_RECEIVER_CAP_SIZE])
+ {
+ return (dpcd[DP_DPCD_REV] >= 0x14) ? DP_TRAINING_PATTERN_MASK_1_4 :
+ DP_TRAINING_PATTERN_MASK;
+ }
+
+ static inline bool
+ drm_dp_is_branch(const u8 dpcd[DP_RECEIVER_CAP_SIZE])
+ {
+ return dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_PRESENT;
+ }
+
+ /* DP/eDP DSC support */
+ u8 drm_dp_dsc_sink_max_slice_count(const u8 dsc_dpcd[DP_DSC_RECEIVER_CAP_SIZE],
+ bool is_edp);
+ u8 drm_dp_dsc_sink_line_buf_depth(const u8 dsc_dpcd[DP_DSC_RECEIVER_CAP_SIZE]);
+ int drm_dp_dsc_sink_supported_input_bpcs(const u8 dsc_dpc[DP_DSC_RECEIVER_CAP_SIZE],
+ u8 dsc_bpc[3]);
+
+ static inline bool
+ drm_dp_sink_supports_dsc(const u8 dsc_dpcd[DP_DSC_RECEIVER_CAP_SIZE])
+ {
+ return dsc_dpcd[DP_DSC_SUPPORT - DP_DSC_SUPPORT] &
+ DP_DSC_DECOMPRESSION_IS_SUPPORTED;
+ }
+
+ static inline u16
+ drm_edp_dsc_sink_output_bpp(const u8 dsc_dpcd[DP_DSC_RECEIVER_CAP_SIZE])
+ {
+ return dsc_dpcd[DP_DSC_MAX_BITS_PER_PIXEL_LOW - DP_DSC_SUPPORT] |
+ (dsc_dpcd[DP_DSC_MAX_BITS_PER_PIXEL_HI - DP_DSC_SUPPORT] &
+ DP_DSC_MAX_BITS_PER_PIXEL_HI_MASK <<
+ DP_DSC_MAX_BITS_PER_PIXEL_HI_SHIFT);
+ }
+
+ static inline u32
+ drm_dp_dsc_sink_max_slice_width(const u8 dsc_dpcd[DP_DSC_RECEIVER_CAP_SIZE])
+ {
+ /* Max Slicewidth = Number of Pixels * 320 */
+ return dsc_dpcd[DP_DSC_MAX_SLICE_WIDTH - DP_DSC_SUPPORT] *
+ DP_DSC_SLICE_WIDTH_MULTIPLIER;
+ }
+
+ /* Forward Error Correction Support on DP 1.4 */
+ static inline bool
+ drm_dp_sink_supports_fec(const u8 fec_capable)
+ {
+ return fec_capable & DP_FEC_CAPABLE;
+ }
+
+ static inline bool
+ drm_dp_channel_coding_supported(const u8 dpcd[DP_RECEIVER_CAP_SIZE])
+ {
+ return dpcd[DP_MAIN_LINK_CHANNEL_CODING] & DP_CAP_ANSI_8B10B;
+ }
+
+ static inline bool
+ drm_dp_alternate_scrambler_reset_cap(const u8 dpcd[DP_RECEIVER_CAP_SIZE])
+ {
+ return dpcd[DP_EDP_CONFIGURATION_CAP] &
+ DP_ALTERNATE_SCRAMBLER_RESET_CAP;
+ }
+
+ /* Ignore MSA timing for Adaptive Sync support on DP 1.4 */
+ static inline bool
+ drm_dp_sink_can_do_video_without_timing_msa(const u8 dpcd[DP_RECEIVER_CAP_SIZE])
+ {
+ return dpcd[DP_DOWN_STREAM_PORT_COUNT] &
+ DP_MSA_TIMING_PAR_IGNORED;
+ }
+
+ /**
+ * drm_edp_backlight_supported() - Check an eDP DPCD for VESA backlight support
+ * @edp_dpcd: The DPCD to check
+ *
+ * Note that currently this function will return %false for panels which support various DPCD
+ * backlight features but which require the brightness be set through PWM, and don't support setting
+ * the brightness level via the DPCD.
+ *
+ * Returns: %True if @edp_dpcd indicates that VESA backlight controls are supported, %false
+ * otherwise
+ */
+ static inline bool
+ drm_edp_backlight_supported(const u8 edp_dpcd[EDP_DISPLAY_CTL_CAP_SIZE])
+ {
+ return !!(edp_dpcd[1] & DP_EDP_TCON_BACKLIGHT_ADJUSTMENT_CAP);
+ }
+
+ /*
+ * DisplayPort AUX channel
+ */
+
+ /**
+ * struct drm_dp_aux_msg - DisplayPort AUX channel transaction
+ * @address: address of the (first) register to access
+ * @request: contains the type of transaction (see DP_AUX_* macros)
+ * @reply: upon completion, contains the reply type of the transaction
+ * @buffer: pointer to a transmission or reception buffer
+ * @size: size of @buffer
+ */
+ struct drm_dp_aux_msg {
+ unsigned int address;
+ u8 request;
+ u8 reply;
+ void *buffer;
+ size_t size;
+ };
+
+ struct cec_adapter;
+ struct edid;
+ struct drm_connector;
+
+ /**
+ * struct drm_dp_aux_cec - DisplayPort CEC-Tunneling-over-AUX
+ * @lock: mutex protecting this struct
+ * @adap: the CEC adapter for CEC-Tunneling-over-AUX support.
+ * @connector: the connector this CEC adapter is associated with
+ * @unregister_work: unregister the CEC adapter
+ */
+ struct drm_dp_aux_cec {
+ struct mutex lock;
+ struct cec_adapter *adap;
+ struct drm_connector *connector;
+ struct delayed_work unregister_work;
+ };
+
+ /**
+ * struct drm_dp_aux - DisplayPort AUX channel
+ *
+ * An AUX channel can also be used to transport I2C messages to a sink. A
+ * typical application of that is to access an EDID that's present in the sink
+ * device. The @transfer() function can also be used to execute such
+ * transactions. The drm_dp_aux_register() function registers an I2C adapter
+ * that can be passed to drm_probe_ddc(). Upon removal, drivers should call
+ * drm_dp_aux_unregister() to remove the I2C adapter. The I2C adapter uses long
+ * transfers by default; if a partial response is received, the adapter will
+ * drop down to the size given by the partial response for this transaction
+ * only.
+ */
+ struct drm_dp_aux {
+ /**
+ * @name: user-visible name of this AUX channel and the
+ * I2C-over-AUX adapter.
+ *
+ * It's also used to specify the name of the I2C adapter. If set
+ * to %NULL, dev_name() of @dev will be used.
+ */
+ const char *name;
+
+ /**
+ * @ddc: I2C adapter that can be used for I2C-over-AUX
+ * communication
+ */
+ struct i2c_adapter ddc;
+
+ /**
+ * @dev: pointer to struct device that is the parent for this
+ * AUX channel.
+ */
+ struct device *dev;
+
+ /**
+ * @drm_dev: pointer to the &drm_device that owns this AUX channel.
+ * Beware, this may be %NULL before drm_dp_aux_register() has been
+ * called.
+ *
+ * It should be set to the &drm_device that will be using this AUX
+ * channel as early as possible. For many graphics drivers this should
+ * happen before drm_dp_aux_init(), however it's perfectly fine to set
+ * this field later so long as it's assigned before calling
+ * drm_dp_aux_register().
+ */
+ struct drm_device *drm_dev;
+
+ /**
+ * @crtc: backpointer to the crtc that is currently using this
+ * AUX channel
+ */
+ struct drm_crtc *crtc;
+
+ /**
+ * @hw_mutex: internal mutex used for locking transfers.
+ *
+ * Note that if the underlying hardware is shared among multiple
+ * channels, the driver needs to do additional locking to
+ * prevent concurrent access.
+ */
+ struct mutex hw_mutex;
+
+ /**
+ * @crc_work: worker that captures CRCs for each frame
+ */
+ struct work_struct crc_work;
+
+ /**
+ * @crc_count: counter of captured frame CRCs
+ */
+ u8 crc_count;
+
+ /**
+ * @transfer: transfers a message representing a single AUX
+ * transaction.
+ *
+ * This is a hardware-specific implementation of how
+ * transactions are executed that the drivers must provide.
+ *
+ * A pointer to a &drm_dp_aux_msg structure describing the
+ * transaction is passed into this function. Upon success, the
+ * implementation should return the number of payload bytes that
+ * were transferred, or a negative error-code on failure.
+ *
+ * Helpers will propagate these errors, with the exception of
+ * the %-EBUSY error, which causes a transaction to be retried.
+ * On a short, helpers will return %-EPROTO to make it simpler
+ * to check for failure.
+ *
+ * The @transfer() function must only modify the reply field of
+ * the &drm_dp_aux_msg structure. The retry logic and i2c
+ * helpers assume this is the case.
+ *
+ * Also note that this callback can be called no matter the
+ * state @dev is in. Drivers that need that device to be powered
+ * to perform this operation will first need to make sure it's
+ * been properly enabled.
+ */
+ ssize_t (*transfer)(struct drm_dp_aux *aux,
+ struct drm_dp_aux_msg *msg);
+
+ /**
+ * @i2c_nack_count: Counts I2C NACKs, used for DP validation.
+ */
+ unsigned i2c_nack_count;
+ /**
+ * @i2c_defer_count: Counts I2C DEFERs, used for DP validation.
+ */
+ unsigned i2c_defer_count;
+ /**
+ * @cec: struct containing fields used for CEC-Tunneling-over-AUX.
+ */
+ struct drm_dp_aux_cec cec;
+ /**
+ * @is_remote: Is this AUX CH actually using sideband messaging.
+ */
+ bool is_remote;
+ };
+
+ ssize_t drm_dp_dpcd_read(struct drm_dp_aux *aux, unsigned int offset,
+ void *buffer, size_t size);
+ ssize_t drm_dp_dpcd_write(struct drm_dp_aux *aux, unsigned int offset,
+ void *buffer, size_t size);
+
+ /**
+ * drm_dp_dpcd_readb() - read a single byte from the DPCD
+ * @aux: DisplayPort AUX channel
+ * @offset: address of the register to read
+ * @valuep: location where the value of the register will be stored
+ *
+ * Returns the number of bytes transferred (1) on success, or a negative
+ * error code on failure.
+ */
+ static inline ssize_t drm_dp_dpcd_readb(struct drm_dp_aux *aux,
+ unsigned int offset, u8 *valuep)
+ {
+ return drm_dp_dpcd_read(aux, offset, valuep, 1);
+ }
+
+ /**
+ * drm_dp_dpcd_writeb() - write a single byte to the DPCD
+ * @aux: DisplayPort AUX channel
+ * @offset: address of the register to write
+ * @value: value to write to the register
+ *
+ * Returns the number of bytes transferred (1) on success, or a negative
+ * error code on failure.
+ */
+ static inline ssize_t drm_dp_dpcd_writeb(struct drm_dp_aux *aux,
+ unsigned int offset, u8 value)
+ {
+ return drm_dp_dpcd_write(aux, offset, &value, 1);
+ }
+
+ int drm_dp_read_dpcd_caps(struct drm_dp_aux *aux,
+ u8 dpcd[DP_RECEIVER_CAP_SIZE]);
+
+ int drm_dp_dpcd_read_link_status(struct drm_dp_aux *aux,
+ u8 status[DP_LINK_STATUS_SIZE]);
+
+ int drm_dp_dpcd_read_phy_link_status(struct drm_dp_aux *aux,
+ enum drm_dp_phy dp_phy,
+ u8 link_status[DP_LINK_STATUS_SIZE]);
+
+ bool drm_dp_send_real_edid_checksum(struct drm_dp_aux *aux,
+ u8 real_edid_checksum);
+
+ int drm_dp_read_downstream_info(struct drm_dp_aux *aux,
+ const u8 dpcd[DP_RECEIVER_CAP_SIZE],
+ u8 downstream_ports[DP_MAX_DOWNSTREAM_PORTS]);
+ bool drm_dp_downstream_is_type(const u8 dpcd[DP_RECEIVER_CAP_SIZE],
+ const u8 port_cap[4], u8 type);
+ bool drm_dp_downstream_is_tmds(const u8 dpcd[DP_RECEIVER_CAP_SIZE],
+ const u8 port_cap[4],
+ const struct edid *edid);
+ int drm_dp_downstream_max_dotclock(const u8 dpcd[DP_RECEIVER_CAP_SIZE],
+ const u8 port_cap[4]);
+ int drm_dp_downstream_max_tmds_clock(const u8 dpcd[DP_RECEIVER_CAP_SIZE],
+ const u8 port_cap[4],
+ const struct edid *edid);
+ int drm_dp_downstream_min_tmds_clock(const u8 dpcd[DP_RECEIVER_CAP_SIZE],
+ const u8 port_cap[4],
+ const struct edid *edid);
+ int drm_dp_downstream_max_bpc(const u8 dpcd[DP_RECEIVER_CAP_SIZE],
+ const u8 port_cap[4],
+ const struct edid *edid);
+ bool drm_dp_downstream_420_passthrough(const u8 dpcd[DP_RECEIVER_CAP_SIZE],
+ const u8 port_cap[4]);
+ bool drm_dp_downstream_444_to_420_conversion(const u8 dpcd[DP_RECEIVER_CAP_SIZE],
+ const u8 port_cap[4]);
+ struct drm_display_mode *drm_dp_downstream_mode(struct drm_device *dev,
+ const u8 dpcd[DP_RECEIVER_CAP_SIZE],
+ const u8 port_cap[4]);
+ int drm_dp_downstream_id(struct drm_dp_aux *aux, char id[6]);
+ void drm_dp_downstream_debug(struct seq_file *m,
+ const u8 dpcd[DP_RECEIVER_CAP_SIZE],
+ const u8 port_cap[4],
+ const struct edid *edid,
+ struct drm_dp_aux *aux);
+ enum drm_mode_subconnector
+ drm_dp_subconnector_type(const u8 dpcd[DP_RECEIVER_CAP_SIZE],
+ const u8 port_cap[4]);
+ void drm_dp_set_subconnector_property(struct drm_connector *connector,
+ enum drm_connector_status status,
+ const u8 *dpcd,
+ const u8 port_cap[4]);
+
+ struct drm_dp_desc;
+ bool drm_dp_read_sink_count_cap(struct drm_connector *connector,
+ const u8 dpcd[DP_RECEIVER_CAP_SIZE],
+ const struct drm_dp_desc *desc);
+ int drm_dp_read_sink_count(struct drm_dp_aux *aux);
+
+ int drm_dp_read_lttpr_common_caps(struct drm_dp_aux *aux,
+ u8 caps[DP_LTTPR_COMMON_CAP_SIZE]);
+ int drm_dp_read_lttpr_phy_caps(struct drm_dp_aux *aux,
+ enum drm_dp_phy dp_phy,
+ u8 caps[DP_LTTPR_PHY_CAP_SIZE]);
+ int drm_dp_lttpr_count(const u8 cap[DP_LTTPR_COMMON_CAP_SIZE]);
+ int drm_dp_lttpr_max_link_rate(const u8 caps[DP_LTTPR_COMMON_CAP_SIZE]);
+ int drm_dp_lttpr_max_lane_count(const u8 caps[DP_LTTPR_COMMON_CAP_SIZE]);
+ bool drm_dp_lttpr_voltage_swing_level_3_supported(const u8 caps[DP_LTTPR_PHY_CAP_SIZE]);
+ bool drm_dp_lttpr_pre_emphasis_level_3_supported(const u8 caps[DP_LTTPR_PHY_CAP_SIZE]);
+
+ void drm_dp_remote_aux_init(struct drm_dp_aux *aux);
+ void drm_dp_aux_init(struct drm_dp_aux *aux);
+ int drm_dp_aux_register(struct drm_dp_aux *aux);
+ void drm_dp_aux_unregister(struct drm_dp_aux *aux);
+
+ int drm_dp_start_crc(struct drm_dp_aux *aux, struct drm_crtc *crtc);
+ int drm_dp_stop_crc(struct drm_dp_aux *aux);
+
+ struct drm_dp_dpcd_ident {
+ u8 oui[3];
+ u8 device_id[6];
+ u8 hw_rev;
+ u8 sw_major_rev;
+ u8 sw_minor_rev;
+ } __packed;
+
+ /**
+ * struct drm_dp_desc - DP branch/sink device descriptor
+ * @ident: DP device identification from DPCD 0x400 (sink) or 0x500 (branch).
+ * @quirks: Quirks; use drm_dp_has_quirk() to query for the quirks.
+ */
+ struct drm_dp_desc {
+ struct drm_dp_dpcd_ident ident;
+ u32 quirks;
+ };
+
+ int drm_dp_read_desc(struct drm_dp_aux *aux, struct drm_dp_desc *desc,
+ bool is_branch);
+
+ /**
+ * enum drm_dp_quirk - Display Port sink/branch device specific quirks
+ *
+ * Display Port sink and branch devices in the wild have a variety of bugs, try
+ * to collect them here. The quirks are shared, but it's up to the drivers to
+ * implement workarounds for them.
+ */
+ enum drm_dp_quirk {
+ /**
+ * @DP_DPCD_QUIRK_CONSTANT_N:
+ *
+ * The device requires main link attributes Mvid and Nvid to be limited
+ * to 16 bits. So will give a constant value (0x8000) for compatability.
+ */
+ DP_DPCD_QUIRK_CONSTANT_N,
+ /**
+ * @DP_DPCD_QUIRK_NO_PSR:
+ *
+ * The device does not support PSR even if reports that it supports or
+ * driver still need to implement proper handling for such device.
+ */
+ DP_DPCD_QUIRK_NO_PSR,
+ /**
+ * @DP_DPCD_QUIRK_NO_SINK_COUNT:
+ *
+ * The device does not set SINK_COUNT to a non-zero value.
+ * The driver should ignore SINK_COUNT during detection. Note that
+ * drm_dp_read_sink_count_cap() automatically checks for this quirk.
+ */
+ DP_DPCD_QUIRK_NO_SINK_COUNT,
+ /**
+ * @DP_DPCD_QUIRK_DSC_WITHOUT_VIRTUAL_DPCD:
+ *
+ * The device supports MST DSC despite not supporting Virtual DPCD.
+ * The DSC caps can be read from the physical aux instead.
+ */
+ DP_DPCD_QUIRK_DSC_WITHOUT_VIRTUAL_DPCD,
+ /**
+ * @DP_DPCD_QUIRK_CAN_DO_MAX_LINK_RATE_3_24_GBPS:
+ *
+ * The device supports a link rate of 3.24 Gbps (multiplier 0xc) despite
+ * the DP_MAX_LINK_RATE register reporting a lower max multiplier.
+ */
+ DP_DPCD_QUIRK_CAN_DO_MAX_LINK_RATE_3_24_GBPS,
+ };
+
+ /**
+ * drm_dp_has_quirk() - does the DP device have a specific quirk
+ * @desc: Device descriptor filled by drm_dp_read_desc()
+ * @quirk: Quirk to query for
+ *
+ * Return true if DP device identified by @desc has @quirk.
+ */
+ static inline bool
+ drm_dp_has_quirk(const struct drm_dp_desc *desc, enum drm_dp_quirk quirk)
+ {
+ return desc->quirks & BIT(quirk);
+ }
+
+ /**
+ * struct drm_edp_backlight_info - Probed eDP backlight info struct
+ * @pwmgen_bit_count: The pwmgen bit count
+ * @pwm_freq_pre_divider: The PWM frequency pre-divider value being used for this backlight, if any
+ * @max: The maximum backlight level that may be set
+ * @lsb_reg_used: Do we also write values to the DP_EDP_BACKLIGHT_BRIGHTNESS_LSB register?
+ * @aux_enable: Does the panel support the AUX enable cap?
+ * @aux_set: Does the panel support setting the brightness through AUX?
+ *
+ * This structure contains various data about an eDP backlight, which can be populated by using
+ * drm_edp_backlight_init().
+ */
+ struct drm_edp_backlight_info {
+ u8 pwmgen_bit_count;
+ u8 pwm_freq_pre_divider;
+ u16 max;
+
+ bool lsb_reg_used : 1;
+ bool aux_enable : 1;
+ bool aux_set : 1;
+ };
+
+ int
+ drm_edp_backlight_init(struct drm_dp_aux *aux, struct drm_edp_backlight_info *bl,
+ u16 driver_pwm_freq_hz, const u8 edp_dpcd[EDP_DISPLAY_CTL_CAP_SIZE],
+ u16 *current_level, u8 *current_mode);
+ int drm_edp_backlight_set_level(struct drm_dp_aux *aux, const struct drm_edp_backlight_info *bl,
+ u16 level);
+ int drm_edp_backlight_enable(struct drm_dp_aux *aux, const struct drm_edp_backlight_info *bl,
+ u16 level);
+ int drm_edp_backlight_disable(struct drm_dp_aux *aux, const struct drm_edp_backlight_info *bl);
+
+ #if IS_ENABLED(CONFIG_DRM_KMS_HELPER) && (IS_BUILTIN(CONFIG_BACKLIGHT_CLASS_DEVICE) || \
+ (IS_MODULE(CONFIG_DRM_KMS_HELPER) && IS_MODULE(CONFIG_BACKLIGHT_CLASS_DEVICE)))
+
+ int drm_panel_dp_aux_backlight(struct drm_panel *panel, struct drm_dp_aux *aux);
+
+ #else
+
+ static inline int drm_panel_dp_aux_backlight(struct drm_panel *panel,
+ struct drm_dp_aux *aux)
+ {
+ return 0;
+ }
+
+ #endif
+
+ #ifdef CONFIG_DRM_DP_CEC
+ void drm_dp_cec_irq(struct drm_dp_aux *aux);
+ void drm_dp_cec_register_connector(struct drm_dp_aux *aux,
+ struct drm_connector *connector);
+ void drm_dp_cec_unregister_connector(struct drm_dp_aux *aux);
+ void drm_dp_cec_set_edid(struct drm_dp_aux *aux, const struct edid *edid);
+ void drm_dp_cec_unset_edid(struct drm_dp_aux *aux);
+ #else
+ static inline void drm_dp_cec_irq(struct drm_dp_aux *aux)
+ {
+ }
+
+ static inline void
+ drm_dp_cec_register_connector(struct drm_dp_aux *aux,
+ struct drm_connector *connector)
+ {
+ }
+
+ static inline void drm_dp_cec_unregister_connector(struct drm_dp_aux *aux)
+ {
+ }
+
+ static inline void drm_dp_cec_set_edid(struct drm_dp_aux *aux,
+ const struct edid *edid)
+ {
+ }
+
+ static inline void drm_dp_cec_unset_edid(struct drm_dp_aux *aux)
+ {
+ }
+
+ #endif
+
+ /**
+ * struct drm_dp_phy_test_params - DP Phy Compliance parameters
+ * @link_rate: Requested Link rate from DPCD 0x219
+ * @num_lanes: Number of lanes requested by sing through DPCD 0x220
+ * @phy_pattern: DP Phy test pattern from DPCD 0x248
+ * @hbr2_reset: DP HBR2_COMPLIANCE_SCRAMBLER_RESET from DCPD 0x24A and 0x24B
+ * @custom80: DP Test_80BIT_CUSTOM_PATTERN from DPCDs 0x250 through 0x259
+ * @enhanced_frame_cap: flag for enhanced frame capability.
+ */
+ struct drm_dp_phy_test_params {
+ int link_rate;
+ u8 num_lanes;
+ u8 phy_pattern;
+ u8 hbr2_reset[2];
+ u8 custom80[10];
+ bool enhanced_frame_cap;
+ };
+
+ int drm_dp_get_phy_test_pattern(struct drm_dp_aux *aux,
+ struct drm_dp_phy_test_params *data);
+ int drm_dp_set_phy_test_pattern(struct drm_dp_aux *aux,
+ struct drm_dp_phy_test_params *data, u8 dp_rev);
+ int drm_dp_get_pcon_max_frl_bw(const u8 dpcd[DP_RECEIVER_CAP_SIZE],
+ const u8 port_cap[4]);
+ int drm_dp_pcon_frl_prepare(struct drm_dp_aux *aux, bool enable_frl_ready_hpd);
+ bool drm_dp_pcon_is_frl_ready(struct drm_dp_aux *aux);
+ int drm_dp_pcon_frl_configure_1(struct drm_dp_aux *aux, int max_frl_gbps,
+ u8 frl_mode);
+ int drm_dp_pcon_frl_configure_2(struct drm_dp_aux *aux, int max_frl_mask,
+ u8 frl_type);
+ int drm_dp_pcon_reset_frl_config(struct drm_dp_aux *aux);
+ int drm_dp_pcon_frl_enable(struct drm_dp_aux *aux);
+
+ bool drm_dp_pcon_hdmi_link_active(struct drm_dp_aux *aux);
+ int drm_dp_pcon_hdmi_link_mode(struct drm_dp_aux *aux, u8 *frl_trained_mask);
+ void drm_dp_pcon_hdmi_frl_link_error_count(struct drm_dp_aux *aux,
+ struct drm_connector *connector);
+ bool drm_dp_pcon_enc_is_dsc_1_2(const u8 pcon_dsc_dpcd[DP_PCON_DSC_ENCODER_CAP_SIZE]);
+ int drm_dp_pcon_dsc_max_slices(const u8 pcon_dsc_dpcd[DP_PCON_DSC_ENCODER_CAP_SIZE]);
+ int drm_dp_pcon_dsc_max_slice_width(const u8 pcon_dsc_dpcd[DP_PCON_DSC_ENCODER_CAP_SIZE]);
+ int drm_dp_pcon_dsc_bpp_incr(const u8 pcon_dsc_dpcd[DP_PCON_DSC_ENCODER_CAP_SIZE]);
+ int drm_dp_pcon_pps_default(struct drm_dp_aux *aux);
+ int drm_dp_pcon_pps_override_buf(struct drm_dp_aux *aux, u8 pps_buf[128]);
+ int drm_dp_pcon_pps_override_param(struct drm_dp_aux *aux, u8 pps_param[6]);
+ bool drm_dp_downstream_rgb_to_ycbcr_conversion(const u8 dpcd[DP_RECEIVER_CAP_SIZE],
+ const u8 port_cap[4], u8 color_spc);
+ int drm_dp_pcon_convert_rgb_to_ycbcr(struct drm_dp_aux *aux, u8 color_spc);
+
+ #endif /* _DRM_DP_HELPER_H_ */
} *apertures;
bool skip_vt_switch; /* no VT switch on suspend/resume required */
+ bool forced_out; /* set when being removed by another driver */
};
static inline struct apertures_struct *alloc_apertures(unsigned int max_num) {
const char *name);
extern int remove_conflicting_framebuffers(struct apertures_struct *a,
const char *name, bool primary);
+extern bool is_firmware_framebuffer(struct apertures_struct *a);
extern int fb_prepare_logo(struct fb_info *fb_info, int rotate);
extern int fb_show_logo(struct fb_info *fb_info, int rotate);
extern char* fb_get_buffer_offset(struct fb_info *info, struct fb_pixmap *buf, u32 size);