drm-misc-next for 6.14:

UAPI Changes:
 - Clarify drm memory stats documentation
 
 Cross-subsystem Changes:
 
 Core Changes:
  - sched: Documentation fixes,
 
 Driver Changes:
  - amdgpu: Track BO memory stats at runtime
  - amdxdna: Various fixes
  - hisilicon: New HIBMC driver
  - bridges:
    - Provide default implementation of atomic_check for HDMI bridges
    - it605: HDCP improvements, MCCS Support
 -----BEGIN PGP SIGNATURE-----
 
 iJUEABMJAB0WIQTkHFbLp4ejekA/qfgnX84Zoj2+dgUCZ3uZUQAKCRAnX84Zoj2+
 dgM8AX4ur9y2eXLVQPS2IhCouWpFsYgSRnCysdVG43vszZ2kcObvlj4UV8nrrv7j
 W6x9FZYBfRLm6ctAUnu05ppm3zSbSdmsocadu1mfoDbShy31Pc5xklnB1u6M3Asw
 3mOtO5dxkA==
 =QZ6P
 -----END PGP SIGNATURE-----

Merge tag 'drm-misc-next-2025-01-06' of https://gitlab.freedesktop.org/drm/misc/kernel into drm-next

drm-misc-next for 6.14:

UAPI Changes:
- Clarify drm memory stats documentation

Cross-subsystem Changes:

Core Changes:
 - sched: Documentation fixes,

Driver Changes:
 - amdgpu: Track BO memory stats at runtime
 - amdxdna: Various fixes
 - hisilicon: New HIBMC driver
 - bridges:
   - Provide default implementation of atomic_check for HDMI bridges
   - it605: HDCP improvements, MCCS Support

Signed-off-by: Dave Airlie <airlied@redhat.com>

From: Maxime Ripard <mripard@redhat.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20250106-augmented-kakapo-of-action-0cf000@houat
This commit is contained in:
Dave Airlie 2025-01-09 15:48:33 +10:00
commit 0739b8ba82
79 changed files with 2516 additions and 606 deletions

View File

@ -290,6 +290,8 @@ properties:
- tianma,tm070jvhg33
# Tianma Micro-electronics TM070RVHG71 7.0" WXGA TFT LCD panel
- tianma,tm070rvhg71
# Topland TIAN-G07017-01 7.0" WSVGA TFT-LCD panel with capacitive touch
- topland,tian-g07017-01
# Toshiba 8.9" WXGA (1280x768) TFT LCD panel
- toshiba,lt089ac29000
# TPK U.S.A. LLC Fusion 7" 800 x 480 (WVGA) LCD panel with capacitive touch

View File

@ -1524,6 +1524,8 @@ patternProperties:
description: Topeet
"^topic,.*":
description: Topic Embedded Systems
"^topland,.*":
description: Topland Electronics (H.K) Co., Ltd.
"^toppoly,.*":
description: TPO (deprecated, use tpo)
deprecated: true

View File

@ -145,57 +145,57 @@ both.
Memory
^^^^^^
- drm-memory-<region>: <uint> [KiB|MiB]
Each possible memory type which can be used to store buffer objects by the
GPU in question shall be given a stable and unique name to be returned as the
string here.
Each possible memory type which can be used to store buffer objects by the GPU
in question shall be given a stable and unique name to be used as the "<region>"
string.
The region name "memory" is reserved to refer to normal system memory.
Value shall reflect the amount of storage currently consumed by the buffer
The value shall reflect the amount of storage currently consumed by the buffer
objects belong to this client, in the respective memory region.
Default unit shall be bytes with optional unit specifiers of 'KiB' or 'MiB'
indicating kibi- or mebi-bytes.
This key is deprecated and is an alias for drm-resident-<region>. Only one of
the two should be present in the output.
- drm-total-<region>: <uint> [KiB|MiB]
The total size of all requested buffers, including both shared and private
memory. The backing store for the buffers does not need to be currently
instantiated to count under this category. To avoid double-counting, if a buffer
has multiple regions where it can be allocated to, the implementation should
consistently select a single region for accounting purposes.
- drm-shared-<region>: <uint> [KiB|MiB]
The total size of buffers that are shared with another file (e.g., have more
than a single handle).
- drm-total-<region>: <uint> [KiB|MiB]
The total size of all created buffers including shared and private memory. The
backing store for the buffers does not have to be currently instantiated to be
counted under this category.
The total size of buffers that are shared with another file (i.e., have more
than one handle). The same requirement to avoid double-counting that applies to
drm-total-<region> also applies here.
- drm-resident-<region>: <uint> [KiB|MiB]
The total size of buffers that are resident (have their backing store present or
instantiated) in the specified region.
The total size of buffers that are resident (i.e., have their backing store
present or instantiated) in the specified region.
This is an alias for drm-memory-<region> and only one of the two should be
present in the output.
- drm-memory-<region>: <uint> [KiB|MiB]
This key is deprecated and is only printed by amdgpu; it is an alias for
drm-resident-<region>.
- drm-purgeable-<region>: <uint> [KiB|MiB]
The total size of buffers that are purgeable.
The total size of buffers that are resident and purgeable.
For example drivers which implement a form of 'madvise' like functionality can
here count buffers which have instantiated backing store, but have been marked
with an equivalent of MADV_DONTNEED.
For example, drivers that implement functionality similar to 'madvise' can count
buffers that have instantiated backing stores but have been marked with an
equivalent of MADV_DONTNEED.
- drm-active-<region>: <uint> [KiB|MiB]
The total size of buffers that are active on one or more engines.
One practical example of this can be presence of unsignaled fences in an GEM
buffer reservation object. Therefore the active category is a subset of
resident.
One practical example of this could be the presence of unsignaled fences in a
GEM buffer reservation object. Therefore, the active category is a subset of the
resident category.
Implementation Details
======================

View File

@ -7081,7 +7081,8 @@ T: git https://gitlab.freedesktop.org/drm/misc/kernel.git
F: drivers/gpu/drm/sun4i/sun8i*
DRM DRIVER FOR ARM PL111 CLCD
S: Orphan
M: Linus Walleij <linus.walleij@linaro.org>
S: Maintained
T: git https://gitlab.freedesktop.org/drm/misc/kernel.git
F: drivers/gpu/drm/pl111/

View File

@ -22,7 +22,7 @@
#include "amdxdna_mailbox.h"
#include "amdxdna_pci_drv.h"
bool force_cmdlist;
static bool force_cmdlist;
module_param(force_cmdlist, bool, 0600);
MODULE_PARM_DESC(force_cmdlist, "Force use command list (Default false)");

View File

@ -25,7 +25,7 @@
#include "amdxdna_mailbox.h"
#include "amdxdna_pci_drv.h"
int aie2_max_col = XRS_MAX_COL;
static int aie2_max_col = XRS_MAX_COL;
module_param(aie2_max_col, uint, 0600);
MODULE_PARM_DESC(aie2_max_col, "Maximum column could be used");
@ -380,9 +380,9 @@ static int aie2_hw_start(struct amdxdna_dev *xdna)
goto stop_psp;
}
mbox_res.ringbuf_base = (u64)ndev->sram_base;
mbox_res.ringbuf_base = ndev->sram_base;
mbox_res.ringbuf_size = pci_resource_len(pdev, xdna->dev_info->sram_bar);
mbox_res.mbox_base = (u64)ndev->mbox_base;
mbox_res.mbox_base = ndev->mbox_base;
mbox_res.mbox_size = MBOX_SIZE(ndev);
mbox_res.name = "xdna_mailbox";
ndev->mbox = xdnam_mailbox_create(&xdna->ddev, &mbox_res);

View File

@ -269,7 +269,7 @@ int aie2_query_firmware_version(struct amdxdna_dev_hdl *ndev,
int aie2_create_context(struct amdxdna_dev_hdl *ndev, struct amdxdna_hwctx *hwctx);
int aie2_destroy_context(struct amdxdna_dev_hdl *ndev, struct amdxdna_hwctx *hwctx);
int aie2_map_host_buf(struct amdxdna_dev_hdl *ndev, u32 context_id, u64 addr, u64 size);
int aie2_query_status(struct amdxdna_dev_hdl *ndev, char *buf, u32 size, u32 *cols_filled);
int aie2_query_status(struct amdxdna_dev_hdl *ndev, char __user *buf, u32 size, u32 *cols_filled);
int aie2_register_asyn_event_msg(struct amdxdna_dev_hdl *ndev, dma_addr_t addr, u32 size,
void *handle, int (*cb)(void*, const u32 *, size_t));
int aie2_config_cu(struct amdxdna_hwctx *hwctx);

View File

@ -98,27 +98,27 @@ struct mailbox_msg {
static void mailbox_reg_write(struct mailbox_channel *mb_chann, u32 mbox_reg, u32 data)
{
struct xdna_mailbox_res *mb_res = &mb_chann->mb->res;
u64 ringbuf_addr = mb_res->mbox_base + mbox_reg;
void __iomem *ringbuf_addr = mb_res->mbox_base + mbox_reg;
writel(data, (void *)ringbuf_addr);
writel(data, ringbuf_addr);
}
static u32 mailbox_reg_read(struct mailbox_channel *mb_chann, u32 mbox_reg)
{
struct xdna_mailbox_res *mb_res = &mb_chann->mb->res;
u64 ringbuf_addr = mb_res->mbox_base + mbox_reg;
void __iomem *ringbuf_addr = mb_res->mbox_base + mbox_reg;
return readl((void *)ringbuf_addr);
return readl(ringbuf_addr);
}
static int mailbox_reg_read_non_zero(struct mailbox_channel *mb_chann, u32 mbox_reg, u32 *val)
{
struct xdna_mailbox_res *mb_res = &mb_chann->mb->res;
u64 ringbuf_addr = mb_res->mbox_base + mbox_reg;
void __iomem *ringbuf_addr = mb_res->mbox_base + mbox_reg;
int ret, value;
/* Poll till value is not zero */
ret = readx_poll_timeout(readl, (void *)ringbuf_addr, value,
ret = readx_poll_timeout(readl, ringbuf_addr, value,
value, 1 /* us */, 100);
if (ret < 0)
return ret;
@ -200,10 +200,10 @@ static void mailbox_release_msg(struct mailbox_channel *mb_chann,
static int
mailbox_send_msg(struct mailbox_channel *mb_chann, struct mailbox_msg *mb_msg)
{
void __iomem *write_addr;
u32 ringbuf_size;
u32 head, tail;
u32 start_addr;
u64 write_addr;
u32 tmp_tail;
head = mailbox_get_headptr(mb_chann, CHAN_RES_X2I);
@ -221,14 +221,14 @@ mailbox_send_msg(struct mailbox_channel *mb_chann, struct mailbox_msg *mb_msg)
if (tail >= head && tmp_tail > ringbuf_size - sizeof(u32)) {
write_addr = mb_chann->mb->res.ringbuf_base + start_addr + tail;
writel(TOMBSTONE, (void *)write_addr);
writel(TOMBSTONE, write_addr);
/* tombstone is set. Write from the start of the ringbuf */
tail = 0;
}
write_addr = mb_chann->mb->res.ringbuf_base + start_addr + tail;
memcpy_toio((void *)write_addr, &mb_msg->pkg, mb_msg->pkg_size);
memcpy_toio(write_addr, &mb_msg->pkg, mb_msg->pkg_size);
mailbox_set_tailptr(mb_chann, tail + mb_msg->pkg_size);
trace_mbox_set_tail(MAILBOX_NAME, mb_chann->msix_irq,
@ -275,11 +275,11 @@ mailbox_get_resp(struct mailbox_channel *mb_chann, struct xdna_msg_header *heade
static int mailbox_get_msg(struct mailbox_channel *mb_chann)
{
struct xdna_msg_header header;
void __iomem *read_addr;
u32 msg_size, rest;
u32 ringbuf_size;
u32 head, tail;
u32 start_addr;
u64 read_addr;
int ret;
if (mailbox_reg_read_non_zero(mb_chann, mb_chann->res[CHAN_RES_I2X].mb_tail_ptr_reg, &tail))
@ -302,7 +302,7 @@ static int mailbox_get_msg(struct mailbox_channel *mb_chann)
/* Peek size of the message or TOMBSTONE */
read_addr = mb_chann->mb->res.ringbuf_base + start_addr + head;
header.total_size = readl((void *)read_addr);
header.total_size = readl(read_addr);
/* size is TOMBSTONE, set next read from 0 */
if (header.total_size == TOMBSTONE) {
if (head < tail) {
@ -328,7 +328,7 @@ static int mailbox_get_msg(struct mailbox_channel *mb_chann)
rest = sizeof(header) - sizeof(u32);
read_addr += sizeof(u32);
memcpy_fromio((u32 *)&header + 1, (void *)read_addr, rest);
memcpy_fromio((u32 *)&header + 1, read_addr, rest);
read_addr += rest;
ret = mailbox_get_resp(mb_chann, &header, (u32 *)read_addr);

View File

@ -39,9 +39,9 @@ struct xdna_mailbox_msg {
* @mbox_size: mailbox size
*/
struct xdna_mailbox_res {
u64 ringbuf_base;
void __iomem *ringbuf_base;
size_t ringbuf_size;
u64 mbox_base;
void __iomem *mbox_base;
size_t mbox_size;
const char *name;
};

View File

@ -61,7 +61,7 @@ static int amdxdna_drm_open(struct drm_device *ddev, struct drm_file *filp)
goto put_rpm;
}
client->pid = pid_nr(filp->pid);
client->pid = pid_nr(rcu_access_pointer(filp->pid));
client->xdna = xdna;
client->sva = iommu_sva_bind_device(xdna->ddev.dev, current->mm);

View File

@ -62,7 +62,7 @@ const struct dpm_clk_freq npu1_dpm_clk_table[] = {
{ 0 }
};
const struct amdxdna_dev_priv npu1_dev_priv = {
static const struct amdxdna_dev_priv npu1_dev_priv = {
.fw_path = "amdnpu/1502_00/npu.sbin",
.protocol_major = 0x5,
.protocol_minor = 0x7,

View File

@ -61,7 +61,7 @@
#define NPU2_SMU_BAR_BASE MMNPU_APERTURE4_BASE
#define NPU2_SRAM_BAR_BASE MMNPU_APERTURE1_BASE
const struct amdxdna_dev_priv npu2_dev_priv = {
static const struct amdxdna_dev_priv npu2_dev_priv = {
.fw_path = "amdnpu/17f0_00/npu.sbin",
.protocol_major = 0x6,
.protocol_minor = 0x6,

View File

@ -82,7 +82,7 @@ const struct dpm_clk_freq npu4_dpm_clk_table[] = {
{ 0 }
};
const struct amdxdna_dev_priv npu4_dev_priv = {
static const struct amdxdna_dev_priv npu4_dev_priv = {
.fw_path = "amdnpu/17f0_10/npu.sbin",
.protocol_major = 0x6,
.protocol_minor = 12,

View File

@ -61,7 +61,7 @@
#define NPU5_SMU_BAR_BASE MMNPU_APERTURE4_BASE
#define NPU5_SRAM_BAR_BASE MMNPU_APERTURE1_BASE
const struct amdxdna_dev_priv npu5_dev_priv = {
static const struct amdxdna_dev_priv npu5_dev_priv = {
.fw_path = "amdnpu/17f0_11/npu.sbin",
.protocol_major = 0x6,
.protocol_minor = 12,

View File

@ -61,7 +61,7 @@
#define NPU6_SMU_BAR_BASE MMNPU_APERTURE4_BASE
#define NPU6_SRAM_BAR_BASE MMNPU_APERTURE1_BASE
const struct amdxdna_dev_priv npu6_dev_priv = {
static const struct amdxdna_dev_priv npu6_dev_priv = {
.fw_path = "amdnpu/17f0_10/npu.sbin",
.protocol_major = 0x6,
.protocol_minor = 12,

View File

@ -1105,7 +1105,7 @@ static int amdgpu_cs_vm_handling(struct amdgpu_cs_parser *p)
* We can't use gang submit on with reserved VMIDs when the VM changes
* can't be invalidated by more than one engine at the same time.
*/
if (p->gang_size > 1 && !p->adev->vm_manager.concurrent_flush) {
if (p->gang_size > 1 && !adev->vm_manager.concurrent_flush) {
for (i = 0; i < p->gang_size; ++i) {
struct drm_sched_entity *entity = p->entities[i];
struct drm_gpu_scheduler *sched = entity->rq->sched;
@ -1189,7 +1189,7 @@ static int amdgpu_cs_vm_handling(struct amdgpu_cs_parser *p)
if (!bo)
continue;
amdgpu_vm_bo_invalidate(adev, bo, false);
amdgpu_vm_bo_invalidate(bo, false);
}
}

View File

@ -36,6 +36,7 @@
#include "amdgpu_gem.h"
#include "amdgpu_dma_buf.h"
#include "amdgpu_xgmi.h"
#include "amdgpu_vm.h"
#include <drm/amdgpu_drm.h>
#include <drm/ttm/ttm_tt.h>
#include <linux/dma-buf.h>
@ -60,6 +61,8 @@ static int amdgpu_dma_buf_attach(struct dma_buf *dmabuf,
if (pci_p2pdma_distance(adev->pdev, attach->dev, false) < 0)
attach->peer2peer = false;
amdgpu_vm_bo_update_shared(bo);
return 0;
}
@ -345,7 +348,7 @@ amdgpu_dma_buf_move_notify(struct dma_buf_attachment *attach)
/* FIXME: This should be after the "if", but needs a fix to make sure
* DMABuf imports are initialized in the right VM list.
*/
amdgpu_vm_bo_invalidate(adev, bo, false);
amdgpu_vm_bo_invalidate(bo, false);
if (!bo->tbo.resource || bo->tbo.resource->mem_type == TTM_PL_SYSTEM)
return;

View File

@ -60,7 +60,7 @@ void amdgpu_show_fdinfo(struct drm_printer *p, struct drm_file *file)
struct amdgpu_fpriv *fpriv = file->driver_priv;
struct amdgpu_vm *vm = &fpriv->vm;
struct amdgpu_mem_stats stats[__AMDGPU_PL_LAST + 1] = { };
struct amdgpu_mem_stats stats[__AMDGPU_PL_NUM];
ktime_t usage[AMDGPU_HW_IP_NUM];
const char *pl_name[] = {
[TTM_PL_VRAM] = "vram",
@ -72,15 +72,8 @@ void amdgpu_show_fdinfo(struct drm_printer *p, struct drm_file *file)
[AMDGPU_PL_DOORBELL] = "doorbell",
};
unsigned int hw_ip, i;
int ret;
ret = amdgpu_bo_reserve(vm->root.bo, false);
if (ret)
return;
amdgpu_vm_get_memory(vm, stats, ARRAY_SIZE(stats));
amdgpu_bo_unreserve(vm->root.bo);
amdgpu_vm_get_memory(vm, stats);
amdgpu_ctx_mgr_usage(&fpriv->ctx_mgr, usage);
/*
@ -114,9 +107,11 @@ void amdgpu_show_fdinfo(struct drm_printer *p, struct drm_file *file)
drm_printf(p, "amd-evicted-vram:\t%llu KiB\n",
stats[TTM_PL_VRAM].evicted/1024UL);
drm_printf(p, "amd-requested-vram:\t%llu KiB\n",
stats[TTM_PL_VRAM].requested/1024UL);
(stats[TTM_PL_VRAM].drm.shared +
stats[TTM_PL_VRAM].drm.private) / 1024UL);
drm_printf(p, "amd-requested-gtt:\t%llu KiB\n",
stats[TTM_PL_TT].requested/1024UL);
(stats[TTM_PL_TT].drm.shared +
stats[TTM_PL_TT].drm.private) / 1024UL);
for (hw_ip = 0; hw_ip < AMDGPU_HW_IP_NUM; ++hw_ip) {
if (!usage[hw_ip])

View File

@ -42,6 +42,7 @@
#include "amdgpu_dma_buf.h"
#include "amdgpu_hmm.h"
#include "amdgpu_xgmi.h"
#include "amdgpu_vm.h"
static vm_fault_t amdgpu_gem_fault(struct vm_fault *vmf)
{
@ -179,6 +180,7 @@ static int amdgpu_gem_object_open(struct drm_gem_object *obj,
if (r)
return r;
amdgpu_vm_bo_update_shared(abo);
bo_va = amdgpu_vm_bo_find(vm, abo);
if (!bo_va)
bo_va = amdgpu_vm_bo_add(adev, vm, abo);
@ -252,6 +254,7 @@ static void amdgpu_gem_object_close(struct drm_gem_object *obj,
goto out_unlock;
amdgpu_vm_bo_del(adev, bo_va);
amdgpu_vm_bo_update_shared(bo);
if (!amdgpu_vm_ready(vm))
goto out_unlock;
@ -839,7 +842,6 @@ int amdgpu_gem_va_ioctl(struct drm_device *dev, void *data,
int amdgpu_gem_op_ioctl(struct drm_device *dev, void *data,
struct drm_file *filp)
{
struct amdgpu_device *adev = drm_to_adev(dev);
struct drm_amdgpu_gem_op *args = data;
struct drm_gem_object *gobj;
struct amdgpu_vm_bo_base *base;
@ -899,7 +901,7 @@ int amdgpu_gem_op_ioctl(struct drm_device *dev, void *data,
robj->allowed_domains |= AMDGPU_GEM_DOMAIN_GTT;
if (robj->flags & AMDGPU_GEM_CREATE_VM_ALWAYS_VALID)
amdgpu_vm_bo_invalidate(adev, robj, true);
amdgpu_vm_bo_invalidate(robj, true);
amdgpu_bo_unreserve(robj);
break;

View File

@ -1251,7 +1251,6 @@ void amdgpu_bo_move_notify(struct ttm_buffer_object *bo,
bool evict,
struct ttm_resource *new_mem)
{
struct amdgpu_device *adev = amdgpu_ttm_adev(bo->bdev);
struct ttm_resource *old_mem = bo->resource;
struct amdgpu_bo *abo;
@ -1259,7 +1258,7 @@ void amdgpu_bo_move_notify(struct ttm_buffer_object *bo,
return;
abo = ttm_to_amdgpu_bo(bo);
amdgpu_vm_bo_invalidate(adev, abo, evict);
amdgpu_vm_bo_move(abo, new_mem, evict);
amdgpu_bo_kunmap(abo);
@ -1272,75 +1271,6 @@ void amdgpu_bo_move_notify(struct ttm_buffer_object *bo,
old_mem ? old_mem->mem_type : -1);
}
void amdgpu_bo_get_memory(struct amdgpu_bo *bo,
struct amdgpu_mem_stats *stats,
unsigned int sz)
{
const unsigned int domain_to_pl[] = {
[ilog2(AMDGPU_GEM_DOMAIN_CPU)] = TTM_PL_SYSTEM,
[ilog2(AMDGPU_GEM_DOMAIN_GTT)] = TTM_PL_TT,
[ilog2(AMDGPU_GEM_DOMAIN_VRAM)] = TTM_PL_VRAM,
[ilog2(AMDGPU_GEM_DOMAIN_GDS)] = AMDGPU_PL_GDS,
[ilog2(AMDGPU_GEM_DOMAIN_GWS)] = AMDGPU_PL_GWS,
[ilog2(AMDGPU_GEM_DOMAIN_OA)] = AMDGPU_PL_OA,
[ilog2(AMDGPU_GEM_DOMAIN_DOORBELL)] = AMDGPU_PL_DOORBELL,
};
struct amdgpu_device *adev = amdgpu_ttm_adev(bo->tbo.bdev);
struct ttm_resource *res = bo->tbo.resource;
struct drm_gem_object *obj = &bo->tbo.base;
uint64_t size = amdgpu_bo_size(bo);
unsigned int type;
if (!res) {
/*
* If no backing store use one of the preferred domain for basic
* stats. We take the MSB since that should give a reasonable
* view.
*/
BUILD_BUG_ON(TTM_PL_VRAM < TTM_PL_TT ||
TTM_PL_VRAM < TTM_PL_SYSTEM);
type = fls(bo->preferred_domains & AMDGPU_GEM_DOMAIN_MASK);
if (!type)
return;
type--;
if (drm_WARN_ON_ONCE(&adev->ddev,
type >= ARRAY_SIZE(domain_to_pl)))
return;
type = domain_to_pl[type];
} else {
type = res->mem_type;
}
if (drm_WARN_ON_ONCE(&adev->ddev, type >= sz))
return;
/* DRM stats common fields: */
if (drm_gem_object_is_shared_for_memory_stats(obj))
stats[type].drm.shared += size;
else
stats[type].drm.private += size;
if (res) {
stats[type].drm.resident += size;
if (!dma_resv_test_signaled(obj->resv, DMA_RESV_USAGE_BOOKKEEP))
stats[type].drm.active += size;
else if (bo->flags & AMDGPU_GEM_CREATE_DISCARDABLE)
stats[type].drm.purgeable += size;
}
/* amdgpu specific stats: */
if (bo->preferred_domains & AMDGPU_GEM_DOMAIN_VRAM) {
stats[TTM_PL_VRAM].requested += size;
if (type != TTM_PL_VRAM)
stats[TTM_PL_VRAM].evicted += size;
} else if (bo->preferred_domains & AMDGPU_GEM_DOMAIN_GTT) {
stats[TTM_PL_TT].requested += size;
}
}
/**
* amdgpu_bo_release_notify - notification about a BO being released
* @bo: pointer to a buffer object
@ -1555,6 +1485,45 @@ u64 amdgpu_bo_gpu_offset_no_check(struct amdgpu_bo *bo)
return amdgpu_gmc_sign_extend(offset);
}
/**
* amdgpu_bo_mem_stats_placement - bo placement for memory accounting
* @bo: the buffer object we should look at
*
* BO can have multiple preferred placements, to avoid double counting we want
* to file it under a single placement for memory stats.
* Luckily, if we take the highest set bit in preferred_domains the result is
* quite sensible.
*
* Returns:
* Which of the placements should the BO be accounted under.
*/
uint32_t amdgpu_bo_mem_stats_placement(struct amdgpu_bo *bo)
{
uint32_t domain = bo->preferred_domains & AMDGPU_GEM_DOMAIN_MASK;
if (!domain)
return TTM_PL_SYSTEM;
switch (rounddown_pow_of_two(domain)) {
case AMDGPU_GEM_DOMAIN_CPU:
return TTM_PL_SYSTEM;
case AMDGPU_GEM_DOMAIN_GTT:
return TTM_PL_TT;
case AMDGPU_GEM_DOMAIN_VRAM:
return TTM_PL_VRAM;
case AMDGPU_GEM_DOMAIN_GDS:
return AMDGPU_PL_GDS;
case AMDGPU_GEM_DOMAIN_GWS:
return AMDGPU_PL_GWS;
case AMDGPU_GEM_DOMAIN_OA:
return AMDGPU_PL_OA;
case AMDGPU_GEM_DOMAIN_DOORBELL:
return AMDGPU_PL_DOORBELL;
default:
return TTM_PL_SYSTEM;
}
}
/**
* amdgpu_bo_get_preferred_domain - get preferred domain
* @adev: amdgpu device object

View File

@ -305,9 +305,7 @@ int amdgpu_bo_sync_wait_resv(struct amdgpu_device *adev, struct dma_resv *resv,
int amdgpu_bo_sync_wait(struct amdgpu_bo *bo, void *owner, bool intr);
u64 amdgpu_bo_gpu_offset(struct amdgpu_bo *bo);
u64 amdgpu_bo_gpu_offset_no_check(struct amdgpu_bo *bo);
void amdgpu_bo_get_memory(struct amdgpu_bo *bo,
struct amdgpu_mem_stats *stats,
unsigned int size);
uint32_t amdgpu_bo_mem_stats_placement(struct amdgpu_bo *bo);
uint32_t amdgpu_bo_get_preferred_domain(struct amdgpu_device *adev,
uint32_t domain);

View File

@ -26,15 +26,15 @@
#include <linux/dma-direction.h>
#include <drm/gpu_scheduler.h>
#include <drm/ttm/ttm_placement.h>
#include "amdgpu_vram_mgr.h"
#include "amdgpu.h"
#define AMDGPU_PL_GDS (TTM_PL_PRIV + 0)
#define AMDGPU_PL_GWS (TTM_PL_PRIV + 1)
#define AMDGPU_PL_OA (TTM_PL_PRIV + 2)
#define AMDGPU_PL_PREEMPT (TTM_PL_PRIV + 3)
#define AMDGPU_PL_DOORBELL (TTM_PL_PRIV + 4)
#define __AMDGPU_PL_LAST (TTM_PL_PRIV + 4)
#define __AMDGPU_PL_NUM (TTM_PL_PRIV + 5)
#define AMDGPU_GTT_MAX_TRANSFER_SIZE 512
#define AMDGPU_GTT_NUM_TRANSFER_WINDOWS 2

View File

@ -36,6 +36,7 @@
#include <drm/ttm/ttm_tt.h>
#include <drm/drm_exec.h>
#include "amdgpu.h"
#include "amdgpu_vm.h"
#include "amdgpu_trace.h"
#include "amdgpu_amdkfd.h"
#include "amdgpu_gmc.h"
@ -310,6 +311,111 @@ static void amdgpu_vm_bo_reset_state_machine(struct amdgpu_vm *vm)
spin_unlock(&vm->status_lock);
}
/**
* amdgpu_vm_update_shared - helper to update shared memory stat
* @base: base structure for tracking BO usage in a VM
*
* Takes the vm status_lock and updates the shared memory stat. If the basic
* stat changed (e.g. buffer was moved) amdgpu_vm_update_stats need to be called
* as well.
*/
static void amdgpu_vm_update_shared(struct amdgpu_vm_bo_base *base)
{
struct amdgpu_vm *vm = base->vm;
struct amdgpu_bo *bo = base->bo;
uint64_t size = amdgpu_bo_size(bo);
uint32_t bo_memtype = amdgpu_bo_mem_stats_placement(bo);
bool shared;
spin_lock(&vm->status_lock);
shared = drm_gem_object_is_shared_for_memory_stats(&bo->tbo.base);
if (base->shared != shared) {
base->shared = shared;
if (shared) {
vm->stats[bo_memtype].drm.shared += size;
vm->stats[bo_memtype].drm.private -= size;
} else {
vm->stats[bo_memtype].drm.shared -= size;
vm->stats[bo_memtype].drm.private += size;
}
}
spin_unlock(&vm->status_lock);
}
/**
* amdgpu_vm_bo_update_shared - callback when bo gets shared/unshared
* @bo: amdgpu buffer object
*
* Update the per VM stats for all the vm if needed from private to shared or
* vice versa.
*/
void amdgpu_vm_bo_update_shared(struct amdgpu_bo *bo)
{
struct amdgpu_vm_bo_base *base;
for (base = bo->vm_bo; base; base = base->next)
amdgpu_vm_update_shared(base);
}
/**
* amdgpu_vm_update_stats_locked - helper to update normal memory stat
* @base: base structure for tracking BO usage in a VM
* @res: the ttm_resource to use for the purpose of accounting, may or may not
* be bo->tbo.resource
* @sign: if we should add (+1) or subtract (-1) from the stat
*
* Caller need to have the vm status_lock held. Useful for when multiple update
* need to happen at the same time.
*/
static void amdgpu_vm_update_stats_locked(struct amdgpu_vm_bo_base *base,
struct ttm_resource *res, int sign)
{
struct amdgpu_vm *vm = base->vm;
struct amdgpu_bo *bo = base->bo;
int64_t size = sign * amdgpu_bo_size(bo);
uint32_t bo_memtype = amdgpu_bo_mem_stats_placement(bo);
/* For drm-total- and drm-shared-, BO are accounted by their preferred
* placement, see also amdgpu_bo_mem_stats_placement.
*/
if (base->shared)
vm->stats[bo_memtype].drm.shared += size;
else
vm->stats[bo_memtype].drm.private += size;
if (res && res->mem_type < __AMDGPU_PL_NUM) {
uint32_t res_memtype = res->mem_type;
vm->stats[res_memtype].drm.resident += size;
/* BO only count as purgeable if it is resident,
* since otherwise there's nothing to purge.
*/
if (bo->flags & AMDGPU_GEM_CREATE_DISCARDABLE)
vm->stats[res_memtype].drm.purgeable += size;
if (!(bo->preferred_domains & amdgpu_mem_type_to_domain(res_memtype)))
vm->stats[bo_memtype].evicted += size;
}
}
/**
* amdgpu_vm_update_stats - helper to update normal memory stat
* @base: base structure for tracking BO usage in a VM
* @res: the ttm_resource to use for the purpose of accounting, may or may not
* be bo->tbo.resource
* @sign: if we should add (+1) or subtract (-1) from the stat
*
* Updates the basic memory stat when bo is added/deleted/moved.
*/
void amdgpu_vm_update_stats(struct amdgpu_vm_bo_base *base,
struct ttm_resource *res, int sign)
{
struct amdgpu_vm *vm = base->vm;
spin_lock(&vm->status_lock);
amdgpu_vm_update_stats_locked(base, res, sign);
spin_unlock(&vm->status_lock);
}
/**
* amdgpu_vm_bo_base_init - Adds bo to the list of bos associated with the vm
*
@ -333,6 +439,11 @@ void amdgpu_vm_bo_base_init(struct amdgpu_vm_bo_base *base,
base->next = bo->vm_bo;
bo->vm_bo = base;
spin_lock(&vm->status_lock);
base->shared = drm_gem_object_is_shared_for_memory_stats(&bo->tbo.base);
amdgpu_vm_update_stats_locked(base, bo->tbo.resource, +1);
spin_unlock(&vm->status_lock);
if (!amdgpu_vm_is_bo_always_valid(vm, bo))
return;
@ -1083,53 +1194,11 @@ int amdgpu_vm_update_range(struct amdgpu_device *adev, struct amdgpu_vm *vm,
return r;
}
static void amdgpu_vm_bo_get_memory(struct amdgpu_bo_va *bo_va,
struct amdgpu_mem_stats *stats,
unsigned int size)
{
struct amdgpu_vm *vm = bo_va->base.vm;
struct amdgpu_bo *bo = bo_va->base.bo;
if (!bo)
return;
/*
* For now ignore BOs which are currently locked and potentially
* changing their location.
*/
if (!amdgpu_vm_is_bo_always_valid(vm, bo) &&
!dma_resv_trylock(bo->tbo.base.resv))
return;
amdgpu_bo_get_memory(bo, stats, size);
if (!amdgpu_vm_is_bo_always_valid(vm, bo))
dma_resv_unlock(bo->tbo.base.resv);
}
void amdgpu_vm_get_memory(struct amdgpu_vm *vm,
struct amdgpu_mem_stats *stats,
unsigned int size)
struct amdgpu_mem_stats stats[__AMDGPU_PL_NUM])
{
struct amdgpu_bo_va *bo_va, *tmp;
spin_lock(&vm->status_lock);
list_for_each_entry_safe(bo_va, tmp, &vm->idle, base.vm_status)
amdgpu_vm_bo_get_memory(bo_va, stats, size);
list_for_each_entry_safe(bo_va, tmp, &vm->evicted, base.vm_status)
amdgpu_vm_bo_get_memory(bo_va, stats, size);
list_for_each_entry_safe(bo_va, tmp, &vm->relocated, base.vm_status)
amdgpu_vm_bo_get_memory(bo_va, stats, size);
list_for_each_entry_safe(bo_va, tmp, &vm->moved, base.vm_status)
amdgpu_vm_bo_get_memory(bo_va, stats, size);
list_for_each_entry_safe(bo_va, tmp, &vm->invalidated, base.vm_status)
amdgpu_vm_bo_get_memory(bo_va, stats, size);
list_for_each_entry_safe(bo_va, tmp, &vm->done, base.vm_status)
amdgpu_vm_bo_get_memory(bo_va, stats, size);
memcpy(stats, vm->stats, sizeof(*stats) * __AMDGPU_PL_NUM);
spin_unlock(&vm->status_lock);
}
@ -2075,6 +2144,7 @@ void amdgpu_vm_bo_del(struct amdgpu_device *adev,
if (*base != &bo_va->base)
continue;
amdgpu_vm_update_stats(*base, bo->tbo.resource, -1);
*base = bo_va->base.next;
break;
}
@ -2143,14 +2213,12 @@ bool amdgpu_vm_evictable(struct amdgpu_bo *bo)
/**
* amdgpu_vm_bo_invalidate - mark the bo as invalid
*
* @adev: amdgpu_device pointer
* @bo: amdgpu buffer object
* @evicted: is the BO evicted
*
* Mark @bo as invalid.
*/
void amdgpu_vm_bo_invalidate(struct amdgpu_device *adev,
struct amdgpu_bo *bo, bool evicted)
void amdgpu_vm_bo_invalidate(struct amdgpu_bo *bo, bool evicted)
{
struct amdgpu_vm_bo_base *bo_base;
@ -2175,6 +2243,32 @@ void amdgpu_vm_bo_invalidate(struct amdgpu_device *adev,
}
}
/**
* amdgpu_vm_bo_move - handle BO move
*
* @bo: amdgpu buffer object
* @new_mem: the new placement of the BO move
* @evicted: is the BO evicted
*
* Update the memory stats for the new placement and mark @bo as invalid.
*/
void amdgpu_vm_bo_move(struct amdgpu_bo *bo, struct ttm_resource *new_mem,
bool evicted)
{
struct amdgpu_vm_bo_base *bo_base;
for (bo_base = bo->vm_bo; bo_base; bo_base = bo_base->next) {
struct amdgpu_vm *vm = bo_base->vm;
spin_lock(&vm->status_lock);
amdgpu_vm_update_stats_locked(bo_base, bo->tbo.resource, -1);
amdgpu_vm_update_stats_locked(bo_base, new_mem, +1);
spin_unlock(&vm->status_lock);
}
amdgpu_vm_bo_invalidate(bo, evicted);
}
/**
* amdgpu_vm_get_block_size - calculate VM page table size as power of two
*
@ -2594,6 +2688,16 @@ void amdgpu_vm_release_compute(struct amdgpu_device *adev, struct amdgpu_vm *vm)
vm->is_compute_context = false;
}
static int amdgpu_vm_stats_is_zero(struct amdgpu_vm *vm)
{
for (int i = 0; i < __AMDGPU_PL_NUM; ++i) {
if (!(drm_memory_stats_is_zero(&vm->stats[i].drm) &&
vm->stats[i].evicted == 0))
return false;
}
return true;
}
/**
* amdgpu_vm_fini - tear down a vm instance
*
@ -2617,7 +2721,6 @@ void amdgpu_vm_fini(struct amdgpu_device *adev, struct amdgpu_vm *vm)
root = amdgpu_bo_ref(vm->root.bo);
amdgpu_bo_reserve(root, true);
amdgpu_vm_put_task_info(vm->task_info);
amdgpu_vm_set_pasid(adev, vm, 0);
dma_fence_wait(vm->last_unlocked, false);
dma_fence_put(vm->last_unlocked);
@ -2666,6 +2769,16 @@ void amdgpu_vm_fini(struct amdgpu_device *adev, struct amdgpu_vm *vm)
}
ttm_lru_bulk_move_fini(&adev->mman.bdev, &vm->lru_bulk_move);
if (!amdgpu_vm_stats_is_zero(vm)) {
struct amdgpu_task_info *ti = vm->task_info;
dev_warn(adev->dev,
"VM memory stats for proc %s(%d) task %s(%d) is non-zero when fini\n",
ti->process_name, ti->pid, ti->task_name, ti->tgid);
}
amdgpu_vm_put_task_info(vm->task_info);
}
/**

View File

@ -35,6 +35,7 @@
#include "amdgpu_sync.h"
#include "amdgpu_ring.h"
#include "amdgpu_ids.h"
#include "amdgpu_ttm.h"
struct drm_exec;
@ -202,9 +203,13 @@ struct amdgpu_vm_bo_base {
/* protected by bo being reserved */
struct amdgpu_vm_bo_base *next;
/* protected by spinlock */
/* protected by vm status_lock */
struct list_head vm_status;
/* if the bo is counted as shared in mem stats
* protected by vm status_lock */
bool shared;
/* protected by the BO being reserved */
bool moved;
};
@ -324,10 +329,7 @@ struct amdgpu_vm_fault_info {
struct amdgpu_mem_stats {
struct drm_memory_stats drm;
/* buffers that requested this placement */
uint64_t requested;
/* buffers that requested this placement
* but are currently evicted */
/* buffers that requested this placement but are currently evicted */
uint64_t evicted;
};
@ -345,6 +347,9 @@ struct amdgpu_vm {
/* Lock to protect vm_bo add/del/move on all lists of vm */
spinlock_t status_lock;
/* Memory statistics for this vm, protected by status_lock */
struct amdgpu_mem_stats stats[__AMDGPU_PL_NUM];
/* Per-VM and PT BOs who needs a validation */
struct list_head evicted;
@ -524,8 +529,12 @@ int amdgpu_vm_bo_update(struct amdgpu_device *adev,
struct amdgpu_bo_va *bo_va,
bool clear);
bool amdgpu_vm_evictable(struct amdgpu_bo *bo);
void amdgpu_vm_bo_invalidate(struct amdgpu_device *adev,
struct amdgpu_bo *bo, bool evicted);
void amdgpu_vm_bo_invalidate(struct amdgpu_bo *bo, bool evicted);
void amdgpu_vm_update_stats(struct amdgpu_vm_bo_base *base,
struct ttm_resource *new_res, int sign);
void amdgpu_vm_bo_update_shared(struct amdgpu_bo *bo);
void amdgpu_vm_bo_move(struct amdgpu_bo *bo, struct ttm_resource *new_mem,
bool evicted);
uint64_t amdgpu_vm_map_gart(const dma_addr_t *pages_addr, uint64_t addr);
struct amdgpu_bo_va *amdgpu_vm_bo_find(struct amdgpu_vm *vm,
struct amdgpu_bo *bo);
@ -576,8 +585,7 @@ void amdgpu_vm_set_task_info(struct amdgpu_vm *vm);
void amdgpu_vm_move_to_lru_tail(struct amdgpu_device *adev,
struct amdgpu_vm *vm);
void amdgpu_vm_get_memory(struct amdgpu_vm *vm,
struct amdgpu_mem_stats *stats,
unsigned int size);
struct amdgpu_mem_stats stats[__AMDGPU_PL_NUM]);
int amdgpu_vm_pt_clear(struct amdgpu_device *adev, struct amdgpu_vm *vm,
struct amdgpu_bo_vm *vmbo, bool immediate);

View File

@ -537,6 +537,7 @@ static void amdgpu_vm_pt_free(struct amdgpu_vm_bo_base *entry)
if (!entry->bo)
return;
amdgpu_vm_update_stats(entry, entry->bo->tbo.resource, -1);
entry->bo->vm_bo = NULL;
ttm_bo_set_bulk_move(&entry->bo->tbo, NULL);

View File

@ -204,7 +204,8 @@ static void audio_shutdown(struct device *dev, void *data)
}
static int adv7511_hdmi_i2s_get_dai_id(struct snd_soc_component *component,
struct device_node *endpoint)
struct device_node *endpoint,
void *data)
{
struct of_endpoint of_ep;
int ret;

View File

@ -1952,7 +1952,8 @@ static void anx7625_audio_shutdown(struct device *dev, void *data)
}
static int anx7625_hdmi_i2s_get_dai_id(struct snd_soc_component *component,
struct device_node *endpoint)
struct device_node *endpoint,
void *data)
{
struct of_endpoint of_ep;
int ret;
@ -2139,49 +2140,6 @@ static void hdcp_check_work_func(struct work_struct *work)
drm_modeset_unlock(&drm_dev->mode_config.connection_mutex);
}
static int anx7625_connector_atomic_check(struct anx7625_data *ctx,
struct drm_connector_state *state)
{
struct device *dev = ctx->dev;
int cp;
dev_dbg(dev, "hdcp state check\n");
cp = state->content_protection;
if (cp == ctx->hdcp_cp)
return 0;
if (cp == DRM_MODE_CONTENT_PROTECTION_DESIRED) {
if (ctx->dp_en) {
dev_dbg(dev, "enable HDCP\n");
anx7625_hdcp_enable(ctx);
queue_delayed_work(ctx->hdcp_workqueue,
&ctx->hdcp_work,
msecs_to_jiffies(2000));
}
}
if (cp == DRM_MODE_CONTENT_PROTECTION_UNDESIRED) {
if (ctx->hdcp_cp != DRM_MODE_CONTENT_PROTECTION_ENABLED) {
dev_err(dev, "current CP is not ENABLED\n");
return -EINVAL;
}
anx7625_hdcp_disable(ctx);
ctx->hdcp_cp = DRM_MODE_CONTENT_PROTECTION_UNDESIRED;
drm_hdcp_update_content_protection(ctx->connector,
ctx->hdcp_cp);
dev_dbg(dev, "update CP to UNDESIRE\n");
}
if (cp == DRM_MODE_CONTENT_PROTECTION_ENABLED) {
dev_err(dev, "Userspace illegal set to PROTECTION ENABLE\n");
return -EINVAL;
}
return 0;
}
static int anx7625_bridge_attach(struct drm_bridge *bridge,
enum drm_bridge_attach_flags flags)
{
@ -2418,7 +2376,7 @@ static int anx7625_bridge_atomic_check(struct drm_bridge *bridge,
anx7625_bridge_mode_fixup(bridge, &crtc_state->mode,
&crtc_state->adjusted_mode);
return anx7625_connector_atomic_check(ctx, conn_state);
return 0;
}
static void anx7625_bridge_atomic_enable(struct drm_bridge *bridge,
@ -2427,6 +2385,7 @@ static void anx7625_bridge_atomic_enable(struct drm_bridge *bridge,
struct anx7625_data *ctx = bridge_to_anx7625(bridge);
struct device *dev = ctx->dev;
struct drm_connector *connector;
struct drm_connector_state *conn_state;
dev_dbg(dev, "drm atomic enable\n");
@ -2441,6 +2400,22 @@ static void anx7625_bridge_atomic_enable(struct drm_bridge *bridge,
_anx7625_hpd_polling(ctx, 5000 * 100);
anx7625_dp_start(ctx);
conn_state = drm_atomic_get_new_connector_state(state->base.state, connector);
if (WARN_ON(!conn_state))
return;
if (conn_state->content_protection == DRM_MODE_CONTENT_PROTECTION_DESIRED) {
if (ctx->dp_en) {
dev_dbg(dev, "enable HDCP\n");
anx7625_hdcp_enable(ctx);
queue_delayed_work(ctx->hdcp_workqueue,
&ctx->hdcp_work,
msecs_to_jiffies(2000));
}
}
}
static void anx7625_bridge_atomic_disable(struct drm_bridge *bridge,
@ -2451,6 +2426,17 @@ static void anx7625_bridge_atomic_disable(struct drm_bridge *bridge,
dev_dbg(dev, "drm atomic disable\n");
flush_workqueue(ctx->hdcp_workqueue);
if (ctx->connector &&
ctx->hdcp_cp == DRM_MODE_CONTENT_PROTECTION_ENABLED) {
anx7625_hdcp_disable(ctx);
ctx->hdcp_cp = DRM_MODE_CONTENT_PROTECTION_DESIRED;
drm_hdcp_update_content_protection(ctx->connector,
ctx->hdcp_cp);
dev_dbg(dev, "update CP to DESIRE\n");
}
ctx->connector = NULL;
anx7625_dp_stop(ctx);

View File

@ -569,15 +569,6 @@ static int it6263_read_edid(void *data, u8 *buf, unsigned int block, size_t len)
return 0;
}
static int it6263_bridge_atomic_check(struct drm_bridge *bridge,
struct drm_bridge_state *bridge_state,
struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state)
{
return drm_atomic_helper_connector_hdmi_check(conn_state->connector,
conn_state->state);
}
static void
it6263_bridge_atomic_disable(struct drm_bridge *bridge,
struct drm_bridge_state *old_bridge_state)
@ -812,7 +803,6 @@ static const struct drm_bridge_funcs it6263_bridge_funcs = {
.mode_valid = it6263_bridge_mode_valid,
.atomic_disable = it6263_bridge_atomic_disable,
.atomic_enable = it6263_bridge_atomic_enable,
.atomic_check = it6263_bridge_atomic_check,
.detect = it6263_bridge_detect,
.edid_read = it6263_bridge_edid_read,
.atomic_get_input_bus_fmts = it6263_bridge_atomic_get_input_bus_fmts,

View File

@ -19,6 +19,7 @@
#include <linux/regulator/consumer.h>
#include <linux/types.h>
#include <linux/wait.h>
#include <linux/bitfield.h>
#include <crypto/hash.h>
@ -126,6 +127,7 @@
#define REG_AUX_OUT_DATA0 0x27
#define REG_AUX_CMD_REQ 0x2B
#define M_AUX_REQ_CMD 0x0F
#define AUX_BUSY BIT(5)
#define REG_AUX_DATA_0_7 0x2C
@ -266,6 +268,18 @@
#define REG_SSC_CTRL1 0x189
#define REG_SSC_CTRL2 0x18A
#define REG_AUX_USER_CTRL 0x190
#define EN_USER_AUX BIT(0)
#define USER_AUX_DONE BIT(1)
#define AUX_EVENT BIT(4)
#define REG_AUX_USER_DATA_REC 0x191
#define M_AUX_IN_REC 0xF0
#define M_AUX_OUT_REC 0x0F
#define REG_AUX_USER_REPLY 0x19A
#define REG_AUX_USER_RXB(n) (n + 0x19B)
#define RBR DP_LINK_BW_1_62
#define HBR DP_LINK_BW_2_7
#define HBR2 DP_LINK_BW_5_4
@ -296,11 +310,13 @@
#define MAX_LANE_COUNT 4
#define MAX_LINK_RATE HBR
#define AUTO_TRAIN_RETRY 3
#define MAX_HDCP_DOWN_STREAM_COUNT 10
#define MAX_HDCP_DOWN_STREAM_COUNT 127
#define MAX_CR_LEVEL 0x03
#define MAX_EQ_LEVEL 0x03
#define AUX_WAIT_TIMEOUT_MS 15
#define AUX_FIFO_MAX_SIZE 32
#define AUX_FIFO_MAX_SIZE 16
#define AUX_I2C_MAX_SIZE 4
#define AUX_I2C_DEFER_RETRY 4
#define PIXEL_CLK_DELAY 1
#define PIXEL_CLK_INVERSE 0
#define ADJUST_PHASE_THRESHOLD 80000
@ -323,7 +339,15 @@
enum aux_cmd_type {
CMD_AUX_NATIVE_READ = 0x0,
CMD_AUX_NATIVE_WRITE = 0x5,
CMD_AUX_GI2C_ADR = 0x08,
CMD_AUX_GI2C_READ = 0x09,
CMD_AUX_GI2C_WRITE = 0x0A,
CMD_AUX_I2C_EDID_READ = 0xB,
CMD_AUX_I2C_READ = 0x0D,
CMD_AUX_I2C_WRITE = 0x0C,
/* KSV read with AUX FIFO extend from CMD_AUX_NATIVE_READ*/
CMD_AUX_GET_KSV_LIST = 0x10,
};
enum aux_cmd_reply {
@ -965,7 +989,8 @@ static ssize_t it6505_aux_operation(struct it6505 *it6505,
it6505_set_bits(it6505, REG_AUX_CTRL, AUX_USER_MODE, AUX_USER_MODE);
aux_op_start:
if (cmd == CMD_AUX_I2C_EDID_READ) {
/* HW AUX FIFO supports only EDID and DCPD KSV FIFO area */
if (cmd == CMD_AUX_I2C_EDID_READ || cmd == CMD_AUX_GET_KSV_LIST) {
/* AUX EDID FIFO has max length of AUX_FIFO_MAX_SIZE bytes. */
size = min_t(size_t, size, AUX_FIFO_MAX_SIZE);
/* Enable AUX FIFO read back and clear FIFO */
@ -996,7 +1021,7 @@ static ssize_t it6505_aux_operation(struct it6505 *it6505,
size);
/* Aux Fire */
it6505_write(it6505, REG_AUX_CMD_REQ, cmd);
it6505_write(it6505, REG_AUX_CMD_REQ, FIELD_GET(M_AUX_REQ_CMD, cmd));
ret = it6505_aux_wait(it6505);
if (ret < 0)
@ -1030,7 +1055,7 @@ static ssize_t it6505_aux_operation(struct it6505 *it6505,
goto aux_op_start;
}
if (cmd == CMD_AUX_I2C_EDID_READ) {
if (cmd == CMD_AUX_I2C_EDID_READ || cmd == CMD_AUX_GET_KSV_LIST) {
for (i = 0; i < size; i++) {
ret = it6505_read(it6505, REG_AUX_DATA_FIFO);
if (ret < 0)
@ -1055,7 +1080,7 @@ static ssize_t it6505_aux_operation(struct it6505 *it6505,
ret = i;
aux_op_err:
if (cmd == CMD_AUX_I2C_EDID_READ) {
if (cmd == CMD_AUX_I2C_EDID_READ || cmd == CMD_AUX_GET_KSV_LIST) {
/* clear AUX FIFO */
it6505_set_bits(it6505, REG_AUX_CTRL,
AUX_EN_FIFO_READ | CLR_EDID_FIFO,
@ -1076,10 +1101,14 @@ static ssize_t it6505_aux_do_transfer(struct it6505 *it6505,
size_t size, enum aux_cmd_reply *reply)
{
int i, ret_size, ret = 0, request_size;
int fifo_max_size = (cmd == CMD_AUX_I2C_EDID_READ || cmd == CMD_AUX_GET_KSV_LIST) ?
AUX_FIFO_MAX_SIZE : 4;
mutex_lock(&it6505->aux_lock);
for (i = 0; i < size; i += 4) {
request_size = min((int)size - i, 4);
i = 0;
do {
request_size = min_t(int, (int)size - i, fifo_max_size);
ret_size = it6505_aux_operation(it6505, cmd, address + i,
buffer + i, request_size,
reply);
@ -1088,14 +1117,170 @@ static ssize_t it6505_aux_do_transfer(struct it6505 *it6505,
goto aux_op_err;
}
i += request_size;
ret += ret_size;
}
} while (i < size);
aux_op_err:
mutex_unlock(&it6505->aux_lock);
return ret;
}
static bool it6505_aux_i2c_reply_defer(u8 reply)
{
if (reply == DP_AUX_NATIVE_REPLY_DEFER || reply == DP_AUX_I2C_REPLY_DEFER)
return true;
return false;
}
static bool it6505_aux_i2c_reply_nack(u8 reply)
{
if (reply == DP_AUX_NATIVE_REPLY_NACK || reply == DP_AUX_I2C_REPLY_NACK)
return true;
return false;
}
static int it6505_aux_i2c_wait(struct it6505 *it6505, u8 *reply)
{
int err = 0;
unsigned long timeout;
struct device *dev = it6505->dev;
timeout = jiffies + msecs_to_jiffies(AUX_WAIT_TIMEOUT_MS) + 1;
do {
if (it6505_read(it6505, REG_AUX_USER_CTRL) & AUX_EVENT)
break;
if (time_after(jiffies, timeout)) {
dev_err(dev, "Timed out waiting AUX I2C, BUSY = %X\n",
it6505_aux_op_finished(it6505));
err = -ETIMEDOUT;
goto end_aux_i2c_wait;
}
usleep_range(300, 800);
} while (!it6505_aux_op_finished(it6505));
*reply = it6505_read(it6505, REG_AUX_USER_REPLY) >> 4;
if (*reply == 0)
goto end_aux_i2c_wait;
if (it6505_aux_i2c_reply_defer(*reply))
err = -EBUSY;
else if (it6505_aux_i2c_reply_nack(*reply))
err = -ENXIO;
end_aux_i2c_wait:
it6505_set_bits(it6505, REG_AUX_USER_CTRL, USER_AUX_DONE, USER_AUX_DONE);
return err;
}
static int it6505_aux_i2c_readb(struct it6505 *it6505, u8 *buf, size_t size, u8 *reply)
{
int ret, i;
int retry;
for (retry = 0; retry < AUX_I2C_DEFER_RETRY; retry++) {
it6505_write(it6505, REG_AUX_CMD_REQ, CMD_AUX_GI2C_READ);
ret = it6505_aux_i2c_wait(it6505, reply);
if (it6505_aux_i2c_reply_defer(*reply))
continue;
if (ret >= 0)
break;
}
for (i = 0; i < size; i++)
buf[i] = it6505_read(it6505, REG_AUX_USER_RXB(0 + i));
return size;
}
static int it6505_aux_i2c_writeb(struct it6505 *it6505, u8 *buf, size_t size, u8 *reply)
{
int i, ret;
int retry;
for (i = 0; i < size; i++)
it6505_write(it6505, REG_AUX_OUT_DATA0 + i, buf[i]);
for (retry = 0; retry < AUX_I2C_DEFER_RETRY; retry++) {
it6505_write(it6505, REG_AUX_CMD_REQ, CMD_AUX_GI2C_WRITE);
ret = it6505_aux_i2c_wait(it6505, reply);
if (it6505_aux_i2c_reply_defer(*reply))
continue;
if (ret >= 0)
break;
}
return size;
}
static ssize_t it6505_aux_i2c_operation(struct it6505 *it6505,
struct drm_dp_aux_msg *msg)
{
int ret;
ssize_t request_size, data_cnt = 0;
u8 *buffer = msg->buffer;
/* set AUX user mode */
it6505_set_bits(it6505, REG_AUX_CTRL,
AUX_USER_MODE | AUX_NO_SEGMENT_WR, AUX_USER_MODE);
it6505_set_bits(it6505, REG_AUX_USER_CTRL, EN_USER_AUX, EN_USER_AUX);
/* clear AUX FIFO */
it6505_set_bits(it6505, REG_AUX_CTRL,
AUX_EN_FIFO_READ | CLR_EDID_FIFO,
AUX_EN_FIFO_READ | CLR_EDID_FIFO);
it6505_set_bits(it6505, REG_AUX_CTRL,
AUX_EN_FIFO_READ | CLR_EDID_FIFO, 0x00);
it6505_write(it6505, REG_AUX_ADR_0_7, 0x00);
it6505_write(it6505, REG_AUX_ADR_8_15, msg->address << 1);
if (msg->size == 0) {
/* IIC Start/STOP dummy write */
it6505_write(it6505, REG_AUX_ADR_16_19, msg->request);
it6505_write(it6505, REG_AUX_CMD_REQ, CMD_AUX_GI2C_ADR);
ret = it6505_aux_i2c_wait(it6505, &msg->reply);
goto end_aux_i2c_transfer;
}
/* IIC data transfer */
data_cnt = 0;
do {
request_size = min_t(ssize_t, msg->size - data_cnt, AUX_I2C_MAX_SIZE);
it6505_write(it6505, REG_AUX_ADR_16_19,
msg->request | ((request_size - 1) << 4));
if ((msg->request & DP_AUX_I2C_READ) == DP_AUX_I2C_READ)
ret = it6505_aux_i2c_readb(it6505, &buffer[data_cnt],
request_size, &msg->reply);
else
ret = it6505_aux_i2c_writeb(it6505, &buffer[data_cnt],
request_size, &msg->reply);
if (ret < 0)
goto end_aux_i2c_transfer;
data_cnt += request_size;
} while (data_cnt < msg->size);
ret = data_cnt;
end_aux_i2c_transfer:
it6505_set_bits(it6505, REG_AUX_USER_CTRL, EN_USER_AUX, 0);
it6505_set_bits(it6505, REG_AUX_CTRL, AUX_USER_MODE, 0);
return ret;
}
static ssize_t it6505_aux_i2c_transfer(struct drm_dp_aux *aux,
struct drm_dp_aux_msg *msg)
{
struct it6505 *it6505 = container_of(aux, struct it6505, aux);
guard(mutex)(&it6505->aux_lock);
return it6505_aux_i2c_operation(it6505, msg);
}
static ssize_t it6505_aux_transfer(struct drm_dp_aux *aux,
struct drm_dp_aux_msg *msg)
{
@ -1105,9 +1290,8 @@ static ssize_t it6505_aux_transfer(struct drm_dp_aux *aux,
int ret;
enum aux_cmd_reply reply;
/* IT6505 doesn't support arbitrary I2C read / write. */
if (is_i2c)
return -EINVAL;
return it6505_aux_i2c_transfer(aux, msg);
switch (msg->request) {
case DP_AUX_NATIVE_READ:
@ -1178,6 +1362,37 @@ static int it6505_get_edid_block(void *data, u8 *buf, unsigned int block,
return 0;
}
static int it6505_get_ksvlist(struct it6505 *it6505, u8 *buf, size_t len)
{
struct device *dev = it6505->dev;
enum aux_cmd_reply reply;
int request_size, ret;
int i = 0;
do {
request_size = min_t(int, (int)len - i, 15);
ret = it6505_aux_do_transfer(it6505, CMD_AUX_GET_KSV_LIST,
DP_AUX_HDCP_KSV_FIFO,
buf + i, request_size, &reply);
DRM_DEV_DEBUG_DRIVER(dev, "request_size = %d, ret =%d", request_size, ret);
if (ret < 0)
return ret;
i += request_size;
} while (i < len);
DRM_DEV_DEBUG_DRIVER(dev, "ksv read cnt = %d down_stream_cnt=%d ", i, i / 5);
for (i = 0 ; i < len; i += 5) {
DRM_DEV_DEBUG_DRIVER(dev, "ksv[%d] = %02X%02X%02X%02X%02X",
i / 5, buf[i], buf[i + 1], buf[i + 2], buf[i + 3], buf[i + 4]);
}
return len;
}
static void it6505_variable_config(struct it6505 *it6505)
{
it6505->link_rate_bw_code = HBR;
@ -1959,7 +2174,7 @@ static int it6505_setup_sha1_input(struct it6505 *it6505, u8 *sha1_input)
{
struct device *dev = it6505->dev;
u8 binfo[2];
int down_stream_count, i, err, msg_count = 0;
int down_stream_count, err, msg_count = 0;
err = it6505_get_dpcd(it6505, DP_AUX_HDCP_BINFO, binfo,
ARRAY_SIZE(binfo));
@ -1984,18 +2199,11 @@ static int it6505_setup_sha1_input(struct it6505 *it6505, u8 *sha1_input)
down_stream_count);
return 0;
}
err = it6505_get_ksvlist(it6505, sha1_input, down_stream_count * 5);
if (err < 0)
return err;
for (i = 0; i < down_stream_count; i++) {
err = it6505_get_dpcd(it6505, DP_AUX_HDCP_KSV_FIFO +
(i % 3) * DRM_HDCP_KSV_LEN,
sha1_input + msg_count,
DRM_HDCP_KSV_LEN);
if (err < 0)
return err;
msg_count += 5;
}
msg_count += down_stream_count * 5;
it6505->hdcp_down_stream_count = down_stream_count;
sha1_input[msg_count++] = binfo[0];
@ -2023,7 +2231,7 @@ static bool it6505_hdcp_part2_ksvlist_check(struct it6505 *it6505)
{
struct device *dev = it6505->dev;
u8 av[5][4], bv[5][4];
int i, err;
int i, err, retry;
i = it6505_setup_sha1_input(it6505, it6505->sha1_input);
if (i <= 0) {
@ -2032,22 +2240,28 @@ static bool it6505_hdcp_part2_ksvlist_check(struct it6505 *it6505)
}
it6505_sha1_digest(it6505, it6505->sha1_input, i, (u8 *)av);
/*1B-05 V' must retry 3 times */
for (retry = 0; retry < 3; retry++) {
err = it6505_get_dpcd(it6505, DP_AUX_HDCP_V_PRIME(0), (u8 *)bv,
sizeof(bv));
err = it6505_get_dpcd(it6505, DP_AUX_HDCP_V_PRIME(0), (u8 *)bv,
sizeof(bv));
if (err < 0) {
dev_err(dev, "Read V' value Fail %d", retry);
continue;
}
if (err < 0) {
dev_err(dev, "Read V' value Fail");
return false;
for (i = 0; i < 5; i++) {
if (bv[i][3] != av[i][0] || bv[i][2] != av[i][1] ||
av[i][1] != av[i][2] || bv[i][0] != av[i][3])
break;
DRM_DEV_DEBUG_DRIVER(dev, "V' all match!! %d, %d", retry, i);
return true;
}
}
for (i = 0; i < 5; i++)
if (bv[i][3] != av[i][0] || bv[i][2] != av[i][1] ||
bv[i][1] != av[i][2] || bv[i][0] != av[i][3])
return false;
DRM_DEV_DEBUG_DRIVER(dev, "V' all match!!");
return true;
DRM_DEV_DEBUG_DRIVER(dev, "V' NOT match!! %d", retry);
return false;
}
static void it6505_hdcp_wait_ksv_list(struct work_struct *work)
@ -2055,12 +2269,13 @@ static void it6505_hdcp_wait_ksv_list(struct work_struct *work)
struct it6505 *it6505 = container_of(work, struct it6505,
hdcp_wait_ksv_list);
struct device *dev = it6505->dev;
unsigned int timeout = 5000;
u8 bstatus = 0;
u8 bstatus;
bool ksv_list_check;
/* 1B-04 wait ksv list for 5s */
unsigned long timeout = jiffies +
msecs_to_jiffies(5000) + 1;
timeout /= 20;
while (timeout > 0) {
for (;;) {
if (!it6505_get_sink_hpd_status(it6505))
return;
@ -2069,27 +2284,23 @@ static void it6505_hdcp_wait_ksv_list(struct work_struct *work)
if (bstatus & DP_BSTATUS_READY)
break;
msleep(20);
timeout--;
}
if (time_after(jiffies, timeout)) {
DRM_DEV_DEBUG_DRIVER(dev, "KSV list wait timeout");
goto timeout;
}
if (timeout == 0) {
DRM_DEV_DEBUG_DRIVER(dev, "timeout and ksv list wait failed");
goto timeout;
msleep(20);
}
ksv_list_check = it6505_hdcp_part2_ksvlist_check(it6505);
DRM_DEV_DEBUG_DRIVER(dev, "ksv list ready, ksv list check %s",
ksv_list_check ? "pass" : "fail");
if (ksv_list_check) {
it6505_set_bits(it6505, REG_HDCP_TRIGGER,
HDCP_TRIGGER_KSV_DONE, HDCP_TRIGGER_KSV_DONE);
if (ksv_list_check)
return;
}
timeout:
it6505_set_bits(it6505, REG_HDCP_TRIGGER,
HDCP_TRIGGER_KSV_DONE | HDCP_TRIGGER_KSV_FAIL,
HDCP_TRIGGER_KSV_DONE | HDCP_TRIGGER_KSV_FAIL);
it6505_start_hdcp(it6505);
}
static void it6505_hdcp_work(struct work_struct *work)
@ -2312,14 +2523,20 @@ static int it6505_process_hpd_irq(struct it6505 *it6505)
DRM_DEV_DEBUG_DRIVER(dev, "dp_irq_vector = 0x%02x", dp_irq_vector);
if (dp_irq_vector & DP_CP_IRQ) {
it6505_set_bits(it6505, REG_HDCP_TRIGGER, HDCP_TRIGGER_CPIRQ,
HDCP_TRIGGER_CPIRQ);
bstatus = it6505_dpcd_read(it6505, DP_AUX_HDCP_BSTATUS);
if (bstatus < 0)
return bstatus;
DRM_DEV_DEBUG_DRIVER(dev, "Bstatus = 0x%02x", bstatus);
/*Check BSTATUS when recive CP_IRQ */
if (bstatus & DP_BSTATUS_R0_PRIME_READY &&
it6505->hdcp_status == HDCP_AUTH_GOING)
it6505_set_bits(it6505, REG_HDCP_TRIGGER, HDCP_TRIGGER_CPIRQ,
HDCP_TRIGGER_CPIRQ);
else if (bstatus & (DP_BSTATUS_REAUTH_REQ | DP_BSTATUS_LINK_FAILURE) &&
it6505->hdcp_status == HDCP_AUTH_DONE)
it6505_start_hdcp(it6505);
}
ret = drm_dp_dpcd_read_link_status(&it6505->aux, link_status);
@ -2456,7 +2673,11 @@ static void it6505_irq_hdcp_ksv_check(struct it6505 *it6505)
{
struct device *dev = it6505->dev;
DRM_DEV_DEBUG_DRIVER(dev, "HDCP event Interrupt");
DRM_DEV_DEBUG_DRIVER(dev, "HDCP repeater R0 event Interrupt");
/* 1B01 HDCP encription should start when R0 is ready*/
it6505_set_bits(it6505, REG_HDCP_TRIGGER,
HDCP_TRIGGER_KSV_DONE, HDCP_TRIGGER_KSV_DONE);
schedule_work(&it6505->hdcp_wait_ksv_list);
}

View File

@ -1466,7 +1466,6 @@ static const struct hdmi_codec_ops it66121_audio_codec_ops = {
.audio_shutdown = it66121_audio_shutdown,
.mute_stream = it66121_audio_mute,
.get_eld = it66121_audio_get_eld,
.no_capture_mute = 1,
};
static int it66121_audio_codec_init(struct it66121_ctx *ctx, struct device *dev)
@ -1476,11 +1475,12 @@ static int it66121_audio_codec_init(struct it66121_ctx *ctx, struct device *dev)
.i2s = 1, /* Only i2s support for now */
.spdif = 0,
.max_i2s_channels = 8,
.no_capture_mute = 1,
};
dev_dbg(dev, "%s\n", __func__);
if (!of_property_read_bool(dev->of_node, "#sound-dai-cells")) {
if (!of_property_present(dev->of_node, "#sound-dai-cells")) {
dev_info(dev, "No \"#sound-dai-cells\", no audio\n");
return 0;
}

View File

@ -45,7 +45,6 @@ struct lt9611 {
struct device_node *dsi1_node;
struct mipi_dsi_device *dsi0;
struct mipi_dsi_device *dsi1;
struct platform_device *audio_pdev;
bool ac_mode;
@ -767,15 +766,6 @@ static enum drm_mode_status lt9611_bridge_mode_valid(struct drm_bridge *bridge,
return MODE_OK;
}
static int lt9611_bridge_atomic_check(struct drm_bridge *bridge,
struct drm_bridge_state *bridge_state,
struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state)
{
return drm_atomic_helper_connector_hdmi_check(conn_state->connector,
conn_state->state);
}
static void lt9611_bridge_atomic_pre_enable(struct drm_bridge *bridge,
struct drm_bridge_state *old_bridge_state)
{
@ -864,6 +854,10 @@ static int lt9611_hdmi_clear_infoframe(struct drm_bridge *bridge,
unsigned int mask;
switch (type) {
case HDMI_INFOFRAME_TYPE_AUDIO:
mask = LT9611_INFOFRAME_AUDIO;
break;
case HDMI_INFOFRAME_TYPE_AVI:
mask = LT9611_INFOFRAME_AVI;
break;
@ -897,6 +891,11 @@ static int lt9611_hdmi_write_infoframe(struct drm_bridge *bridge,
int i;
switch (type) {
case HDMI_INFOFRAME_TYPE_AUDIO:
mask = LT9611_INFOFRAME_AUDIO;
addr = 0x84b2;
break;
case HDMI_INFOFRAME_TYPE_AVI:
mask = LT9611_INFOFRAME_AVI;
addr = 0x8440;
@ -940,6 +939,55 @@ lt9611_hdmi_tmds_char_rate_valid(const struct drm_bridge *bridge,
return MODE_OK;
}
static int lt9611_hdmi_audio_startup(struct drm_connector *connector,
struct drm_bridge *bridge)
{
struct lt9611 *lt9611 = bridge_to_lt9611(bridge);
regmap_write(lt9611->regmap, 0x82d6, 0x8c);
regmap_write(lt9611->regmap, 0x82d7, 0x04);
regmap_write(lt9611->regmap, 0x8406, 0x08);
regmap_write(lt9611->regmap, 0x8407, 0x10);
regmap_write(lt9611->regmap, 0x8434, 0xd5);
return 0;
}
static int lt9611_hdmi_audio_prepare(struct drm_connector *connector,
struct drm_bridge *bridge,
struct hdmi_codec_daifmt *fmt,
struct hdmi_codec_params *hparms)
{
struct lt9611 *lt9611 = bridge_to_lt9611(bridge);
if (hparms->sample_rate == 48000)
regmap_write(lt9611->regmap, 0x840f, 0x2b);
else if (hparms->sample_rate == 96000)
regmap_write(lt9611->regmap, 0x840f, 0xab);
else
return -EINVAL;
regmap_write(lt9611->regmap, 0x8435, 0x00);
regmap_write(lt9611->regmap, 0x8436, 0x18);
regmap_write(lt9611->regmap, 0x8437, 0x00);
return drm_atomic_helper_connector_hdmi_update_audio_infoframe(connector,
&hparms->cea);
}
static void lt9611_hdmi_audio_shutdown(struct drm_connector *connector,
struct drm_bridge *bridge)
{
struct lt9611 *lt9611 = bridge_to_lt9611(bridge);
drm_atomic_helper_connector_hdmi_clear_audio_infoframe(connector);
regmap_write(lt9611->regmap, 0x8406, 0x00);
regmap_write(lt9611->regmap, 0x8407, 0x00);
}
static const struct drm_bridge_funcs lt9611_bridge_funcs = {
.attach = lt9611_bridge_attach,
.mode_valid = lt9611_bridge_mode_valid,
@ -947,7 +995,6 @@ static const struct drm_bridge_funcs lt9611_bridge_funcs = {
.edid_read = lt9611_bridge_edid_read,
.hpd_enable = lt9611_bridge_hpd_enable,
.atomic_check = lt9611_bridge_atomic_check,
.atomic_pre_enable = lt9611_bridge_atomic_pre_enable,
.atomic_enable = lt9611_bridge_atomic_enable,
.atomic_disable = lt9611_bridge_atomic_disable,
@ -960,6 +1007,10 @@ static const struct drm_bridge_funcs lt9611_bridge_funcs = {
.hdmi_tmds_char_rate_valid = lt9611_hdmi_tmds_char_rate_valid,
.hdmi_write_infoframe = lt9611_hdmi_write_infoframe,
.hdmi_clear_infoframe = lt9611_hdmi_clear_infoframe,
.hdmi_audio_startup = lt9611_hdmi_audio_startup,
.hdmi_audio_prepare = lt9611_hdmi_audio_prepare,
.hdmi_audio_shutdown = lt9611_hdmi_audio_shutdown,
};
static int lt9611_parse_dt(struct device *dev,
@ -1013,101 +1064,6 @@ static int lt9611_read_device_rev(struct lt9611 *lt9611)
return ret;
}
static int lt9611_hdmi_hw_params(struct device *dev, void *data,
struct hdmi_codec_daifmt *fmt,
struct hdmi_codec_params *hparms)
{
struct lt9611 *lt9611 = data;
if (hparms->sample_rate == 48000)
regmap_write(lt9611->regmap, 0x840f, 0x2b);
else if (hparms->sample_rate == 96000)
regmap_write(lt9611->regmap, 0x840f, 0xab);
else
return -EINVAL;
regmap_write(lt9611->regmap, 0x8435, 0x00);
regmap_write(lt9611->regmap, 0x8436, 0x18);
regmap_write(lt9611->regmap, 0x8437, 0x00);
return 0;
}
static int lt9611_audio_startup(struct device *dev, void *data)
{
struct lt9611 *lt9611 = data;
regmap_write(lt9611->regmap, 0x82d6, 0x8c);
regmap_write(lt9611->regmap, 0x82d7, 0x04);
regmap_write(lt9611->regmap, 0x8406, 0x08);
regmap_write(lt9611->regmap, 0x8407, 0x10);
regmap_write(lt9611->regmap, 0x8434, 0xd5);
return 0;
}
static void lt9611_audio_shutdown(struct device *dev, void *data)
{
struct lt9611 *lt9611 = data;
regmap_write(lt9611->regmap, 0x8406, 0x00);
regmap_write(lt9611->regmap, 0x8407, 0x00);
}
static int lt9611_hdmi_i2s_get_dai_id(struct snd_soc_component *component,
struct device_node *endpoint)
{
struct of_endpoint of_ep;
int ret;
ret = of_graph_parse_endpoint(endpoint, &of_ep);
if (ret < 0)
return ret;
/*
* HDMI sound should be located as reg = <2>
* Then, it is sound port 0
*/
if (of_ep.port == 2)
return 0;
return -EINVAL;
}
static const struct hdmi_codec_ops lt9611_codec_ops = {
.hw_params = lt9611_hdmi_hw_params,
.audio_shutdown = lt9611_audio_shutdown,
.audio_startup = lt9611_audio_startup,
.get_dai_id = lt9611_hdmi_i2s_get_dai_id,
};
static struct hdmi_codec_pdata codec_data = {
.ops = &lt9611_codec_ops,
.max_i2s_channels = 8,
.i2s = 1,
};
static int lt9611_audio_init(struct device *dev, struct lt9611 *lt9611)
{
codec_data.data = lt9611;
lt9611->audio_pdev =
platform_device_register_data(dev, HDMI_CODEC_DRV_NAME,
PLATFORM_DEVID_AUTO,
&codec_data, sizeof(codec_data));
return PTR_ERR_OR_ZERO(lt9611->audio_pdev);
}
static void lt9611_audio_exit(struct lt9611 *lt9611)
{
if (lt9611->audio_pdev) {
platform_device_unregister(lt9611->audio_pdev);
lt9611->audio_pdev = NULL;
}
}
static int lt9611_probe(struct i2c_client *client)
{
struct lt9611 *lt9611;
@ -1171,6 +1127,9 @@ static int lt9611_probe(struct i2c_client *client)
i2c_set_clientdata(client, lt9611);
/* Disable Audio InfoFrame, enabled by default */
regmap_update_bits(lt9611->regmap, 0x843d, LT9611_INFOFRAME_AUDIO, 0);
lt9611->bridge.funcs = &lt9611_bridge_funcs;
lt9611->bridge.of_node = client->dev.of_node;
lt9611->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID |
@ -1179,6 +1138,9 @@ static int lt9611_probe(struct i2c_client *client)
lt9611->bridge.type = DRM_MODE_CONNECTOR_HDMIA;
lt9611->bridge.vendor = "Lontium";
lt9611->bridge.product = "LT9611";
lt9611->bridge.hdmi_audio_dev = dev;
lt9611->bridge.hdmi_audio_max_i2s_playback_channels = 8;
lt9611->bridge.hdmi_audio_dai_port = 2;
drm_bridge_add(&lt9611->bridge);
@ -1200,10 +1162,6 @@ static int lt9611_probe(struct i2c_client *client)
lt9611_enable_hpd_interrupts(lt9611);
ret = lt9611_audio_init(dev, lt9611);
if (ret)
goto err_remove_bridge;
return 0;
err_remove_bridge:
@ -1224,7 +1182,6 @@ static void lt9611_remove(struct i2c_client *client)
struct lt9611 *lt9611 = i2c_get_clientdata(client);
disable_irq(client->irq);
lt9611_audio_exit(lt9611);
drm_bridge_remove(&lt9611->bridge);
regulator_bulk_disable(ARRAY_SIZE(lt9611->supplies), lt9611->supplies);

View File

@ -522,7 +522,8 @@ static void lt9611uxc_audio_shutdown(struct device *dev, void *data)
}
static int lt9611uxc_hdmi_i2s_get_dai_id(struct snd_soc_component *component,
struct device_node *endpoint)
struct device_node *endpoint,
void *data)
{
struct of_endpoint of_ep;
int ret;

View File

@ -815,7 +815,8 @@ static int sii902x_audio_get_eld(struct device *dev, void *data,
}
static int sii902x_audio_get_dai_id(struct snd_soc_component *component,
struct device_node *endpoint)
struct device_node *endpoint,
void *data)
{
struct of_endpoint of_ep;
int ret;
@ -840,7 +841,6 @@ static const struct hdmi_codec_ops sii902x_audio_codec_ops = {
.mute_stream = sii902x_audio_mute,
.get_eld = sii902x_audio_get_eld,
.get_dai_id = sii902x_audio_get_dai_id,
.no_capture_mute = 1,
};
static int sii902x_audio_codec_init(struct sii902x *sii902x,
@ -863,11 +863,12 @@ static int sii902x_audio_codec_init(struct sii902x *sii902x,
.i2s = 1, /* Only i2s support for now. */
.spdif = 0,
.max_i2s_channels = 0,
.no_capture_mute = 1,
};
u8 lanes[4];
int num_lanes, i;
if (!of_property_read_bool(dev->of_node, "#sound-dai-cells")) {
if (!of_property_present(dev->of_node, "#sound-dai-cells")) {
dev_dbg(dev, "%s: No \"#sound-dai-cells\", no audio\n",
__func__);
return 0;

View File

@ -148,7 +148,8 @@ static int dw_hdmi_i2s_get_eld(struct device *dev, void *data, uint8_t *buf,
}
static int dw_hdmi_i2s_get_dai_id(struct snd_soc_component *component,
struct device_node *endpoint)
struct device_node *endpoint,
void *data)
{
struct of_endpoint of_ep;
int ret;

View File

@ -361,22 +361,6 @@ static int dw_hdmi_qp_config_drm_infoframe(struct dw_hdmi_qp *hdmi,
return 0;
}
static int dw_hdmi_qp_bridge_atomic_check(struct drm_bridge *bridge,
struct drm_bridge_state *bridge_state,
struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state)
{
struct dw_hdmi_qp *hdmi = bridge->driver_private;
int ret;
ret = drm_atomic_helper_connector_hdmi_check(conn_state->connector,
conn_state->state);
if (ret)
dev_dbg(hdmi->dev, "%s failed: %d\n", __func__, ret);
return ret;
}
static void dw_hdmi_qp_bridge_atomic_enable(struct drm_bridge *bridge,
struct drm_bridge_state *old_state)
{
@ -503,7 +487,6 @@ static const struct drm_bridge_funcs dw_hdmi_qp_bridge_funcs = {
.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
.atomic_reset = drm_atomic_helper_bridge_reset,
.atomic_check = dw_hdmi_qp_bridge_atomic_check,
.atomic_enable = dw_hdmi_qp_bridge_atomic_enable,
.atomic_disable = dw_hdmi_qp_bridge_atomic_disable,
.detect = dw_hdmi_qp_bridge_detect,

View File

@ -15,6 +15,7 @@ if DRM_DISPLAY_HELPER
config DRM_BRIDGE_CONNECTOR
bool
select DRM_DISPLAY_HDMI_AUDIO_HELPER
select DRM_DISPLAY_HDMI_STATE_HELPER
help
DRM connector implementation terminating DRM bridge chains.
@ -75,6 +76,12 @@ config DRM_DISPLAY_HDCP_HELPER
help
DRM display helpers for HDCP.
config DRM_DISPLAY_HDMI_AUDIO_HELPER
bool
help
DRM display helpers for HDMI Audio functionality (generic HDMI Codec
implementation).
config DRM_DISPLAY_HDMI_HELPER
bool
help
@ -82,6 +89,7 @@ config DRM_DISPLAY_HDMI_HELPER
config DRM_DISPLAY_HDMI_STATE_HELPER
bool
select DRM_DISPLAY_HDMI_AUDIO_HELPER
select DRM_DISPLAY_HDMI_HELPER
help
DRM KMS state helpers for HDMI.

View File

@ -14,6 +14,8 @@ drm_display_helper-$(CONFIG_DRM_DISPLAY_DP_TUNNEL) += \
drm_display_helper-$(CONFIG_DRM_DISPLAY_DSC_HELPER) += \
drm_dsc_helper.o
drm_display_helper-$(CONFIG_DRM_DISPLAY_HDCP_HELPER) += drm_hdcp_helper.o
drm_display_helper-$(CONFIG_DRM_DISPLAY_HDMI_AUDIO_HELPER) += \
drm_hdmi_audio_helper.o
drm_display_helper-$(CONFIG_DRM_DISPLAY_HDMI_HELPER) += \
drm_hdmi_helper.o \
drm_scdc_helper.o

View File

@ -17,7 +17,9 @@
#include <drm/drm_edid.h>
#include <drm/drm_managed.h>
#include <drm/drm_modeset_helper_vtables.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
#include <drm/display/drm_hdmi_audio_helper.h>
#include <drm/display/drm_hdmi_helper.h>
#include <drm/display/drm_hdmi_state_helper.h>
@ -180,11 +182,15 @@ drm_bridge_connector_detect(struct drm_connector *connector, bool force)
struct drm_bridge_connector *bridge_connector =
to_drm_bridge_connector(connector);
struct drm_bridge *detect = bridge_connector->bridge_detect;
struct drm_bridge *hdmi = bridge_connector->bridge_hdmi;
enum drm_connector_status status;
if (detect) {
status = detect->funcs->detect(detect);
if (hdmi)
drm_atomic_helper_connector_hdmi_hotplug(connector, status);
drm_bridge_connector_hpd_notify(connector, status);
} else {
switch (connector->connector_type) {
@ -203,6 +209,16 @@ drm_bridge_connector_detect(struct drm_connector *connector, bool force)
return status;
}
static void drm_bridge_connector_force(struct drm_connector *connector)
{
struct drm_bridge_connector *bridge_connector =
to_drm_bridge_connector(connector);
struct drm_bridge *hdmi = bridge_connector->bridge_hdmi;
if (hdmi)
drm_atomic_helper_connector_hdmi_force(connector);
}
static void drm_bridge_connector_debugfs_init(struct drm_connector *connector,
struct dentry *root)
{
@ -231,6 +247,7 @@ static void drm_bridge_connector_reset(struct drm_connector *connector)
static const struct drm_connector_funcs drm_bridge_connector_funcs = {
.reset = drm_bridge_connector_reset,
.detect = drm_bridge_connector_detect,
.force = drm_bridge_connector_force,
.fill_modes = drm_helper_probe_single_connector_modes,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
@ -276,6 +293,14 @@ static int drm_bridge_connector_get_modes(struct drm_connector *connector)
to_drm_bridge_connector(connector);
struct drm_bridge *bridge;
/*
* If there is a HDMI bridge, EDID has been updated as a part of
* the .detect(). Just update the modes here.
*/
bridge = bridge_connector->bridge_hdmi;
if (bridge)
return drm_edid_connector_add_modes(connector);
/*
* If display exposes EDID, then we parse that in the normal way to
* build table of supported modes.
@ -313,11 +338,24 @@ drm_bridge_connector_mode_valid(struct drm_connector *connector,
return MODE_OK;
}
static int drm_bridge_connector_atomic_check(struct drm_connector *connector,
struct drm_atomic_state *state)
{
struct drm_bridge_connector *bridge_connector =
to_drm_bridge_connector(connector);
if (bridge_connector->bridge_hdmi)
return drm_atomic_helper_connector_hdmi_check(connector, state);
return 0;
}
static const struct drm_connector_helper_funcs drm_bridge_connector_helper_funcs = {
.get_modes = drm_bridge_connector_get_modes,
.mode_valid = drm_bridge_connector_mode_valid,
.enable_hpd = drm_bridge_connector_enable_hpd,
.disable_hpd = drm_bridge_connector_disable_hpd,
.atomic_check = drm_bridge_connector_atomic_check,
};
static enum drm_mode_status
@ -368,10 +406,94 @@ static int drm_bridge_connector_write_infoframe(struct drm_connector *connector,
return bridge->funcs->hdmi_write_infoframe(bridge, type, buffer, len);
}
static const struct drm_edid *
drm_bridge_connector_read_edid(struct drm_connector *connector)
{
struct drm_bridge_connector *bridge_connector =
to_drm_bridge_connector(connector);
struct drm_bridge *bridge;
bridge = bridge_connector->bridge_edid;
if (!bridge)
return NULL;
return drm_bridge_edid_read(bridge, connector);
}
static const struct drm_connector_hdmi_funcs drm_bridge_connector_hdmi_funcs = {
.tmds_char_rate_valid = drm_bridge_connector_tmds_char_rate_valid,
.clear_infoframe = drm_bridge_connector_clear_infoframe,
.write_infoframe = drm_bridge_connector_write_infoframe,
.read_edid = drm_bridge_connector_read_edid,
};
static int drm_bridge_connector_audio_startup(struct drm_connector *connector)
{
struct drm_bridge_connector *bridge_connector =
to_drm_bridge_connector(connector);
struct drm_bridge *bridge;
bridge = bridge_connector->bridge_hdmi;
if (!bridge)
return -EINVAL;
if (!bridge->funcs->hdmi_audio_startup)
return 0;
return bridge->funcs->hdmi_audio_startup(connector, bridge);
}
static int drm_bridge_connector_audio_prepare(struct drm_connector *connector,
struct hdmi_codec_daifmt *fmt,
struct hdmi_codec_params *hparms)
{
struct drm_bridge_connector *bridge_connector =
to_drm_bridge_connector(connector);
struct drm_bridge *bridge;
bridge = bridge_connector->bridge_hdmi;
if (!bridge)
return -EINVAL;
return bridge->funcs->hdmi_audio_prepare(connector, bridge, fmt, hparms);
}
static void drm_bridge_connector_audio_shutdown(struct drm_connector *connector)
{
struct drm_bridge_connector *bridge_connector =
to_drm_bridge_connector(connector);
struct drm_bridge *bridge;
bridge = bridge_connector->bridge_hdmi;
if (!bridge)
return;
bridge->funcs->hdmi_audio_shutdown(connector, bridge);
}
static int drm_bridge_connector_audio_mute_stream(struct drm_connector *connector,
bool enable, int direction)
{
struct drm_bridge_connector *bridge_connector =
to_drm_bridge_connector(connector);
struct drm_bridge *bridge;
bridge = bridge_connector->bridge_hdmi;
if (!bridge)
return -EINVAL;
if (bridge->funcs->hdmi_audio_mute_stream)
return bridge->funcs->hdmi_audio_mute_stream(connector, bridge,
enable, direction);
else
return -ENOTSUPP;
}
static const struct drm_connector_hdmi_audio_funcs drm_bridge_connector_hdmi_audio_funcs = {
.startup = drm_bridge_connector_audio_startup,
.prepare = drm_bridge_connector_audio_prepare,
.shutdown = drm_bridge_connector_audio_shutdown,
.mute_stream = drm_bridge_connector_audio_mute_stream,
};
/* -----------------------------------------------------------------------------
@ -473,7 +595,9 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
if (connector_type == DRM_MODE_CONNECTOR_Unknown)
return ERR_PTR(-EINVAL);
if (bridge_connector->bridge_hdmi)
if (bridge_connector->bridge_hdmi) {
bridge = bridge_connector->bridge_hdmi;
ret = drmm_connector_hdmi_init(drm, connector,
bridge_connector->bridge_hdmi->vendor,
bridge_connector->bridge_hdmi->product,
@ -482,12 +606,31 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
connector_type, ddc,
supported_formats,
max_bpc);
else
if (ret)
return ERR_PTR(ret);
if (bridge->hdmi_audio_max_i2s_playback_channels ||
bridge->hdmi_audio_spdif_playback) {
if (!bridge->funcs->hdmi_audio_prepare ||
!bridge->funcs->hdmi_audio_shutdown)
return ERR_PTR(-EINVAL);
ret = drm_connector_hdmi_audio_init(connector,
bridge->hdmi_audio_dev,
&drm_bridge_connector_hdmi_audio_funcs,
bridge->hdmi_audio_max_i2s_playback_channels,
bridge->hdmi_audio_spdif_playback,
bridge->hdmi_audio_dai_port);
if (ret)
return ERR_PTR(ret);
}
} else {
ret = drmm_connector_init(drm, connector,
&drm_bridge_connector_funcs,
connector_type, ddc);
if (ret)
return ERR_PTR(ret);
if (ret)
return ERR_PTR(ret);
}
drm_connector_helper_add(connector, &drm_bridge_connector_helper_funcs);

View File

@ -0,0 +1,190 @@
// SPDX-License-Identifier: MIT
/*
* Copyright (c) 2024 Linaro Ltd
*/
#include <linux/mutex.h>
#include <linux/of_graph.h>
#include <linux/platform_device.h>
#include <drm/drm_connector.h>
#include <drm/drm_device.h>
#include <drm/display/drm_hdmi_audio_helper.h>
#include <sound/hdmi-codec.h>
static int drm_connector_hdmi_audio_startup(struct device *dev, void *data)
{
struct drm_connector *connector = data;
const struct drm_connector_hdmi_audio_funcs *funcs =
connector->hdmi_audio.funcs;
if (funcs->startup)
return funcs->startup(connector);
return 0;
}
static int drm_connector_hdmi_audio_prepare(struct device *dev, void *data,
struct hdmi_codec_daifmt *fmt,
struct hdmi_codec_params *hparms)
{
struct drm_connector *connector = data;
const struct drm_connector_hdmi_audio_funcs *funcs =
connector->hdmi_audio.funcs;
return funcs->prepare(connector, fmt, hparms);
}
static void drm_connector_hdmi_audio_shutdown(struct device *dev, void *data)
{
struct drm_connector *connector = data;
const struct drm_connector_hdmi_audio_funcs *funcs =
connector->hdmi_audio.funcs;
return funcs->shutdown(connector);
}
static int drm_connector_hdmi_audio_mute_stream(struct device *dev, void *data,
bool enable, int direction)
{
struct drm_connector *connector = data;
const struct drm_connector_hdmi_audio_funcs *funcs =
connector->hdmi_audio.funcs;
if (funcs->mute_stream)
return funcs->mute_stream(connector, enable, direction);
return -ENOTSUPP;
}
static int drm_connector_hdmi_audio_get_dai_id(struct snd_soc_component *comment,
struct device_node *endpoint,
void *data)
{
struct drm_connector *connector = data;
struct of_endpoint of_ep;
int ret;
if (connector->hdmi_audio.dai_port < 0)
return -ENOTSUPP;
ret = of_graph_parse_endpoint(endpoint, &of_ep);
if (ret < 0)
return ret;
if (of_ep.port == connector->hdmi_audio.dai_port)
return 0;
return -EINVAL;
}
static int drm_connector_hdmi_audio_get_eld(struct device *dev, void *data,
uint8_t *buf, size_t len)
{
struct drm_connector *connector = data;
mutex_lock(&connector->eld_mutex);
memcpy(buf, connector->eld, min(sizeof(connector->eld), len));
mutex_unlock(&connector->eld_mutex);
return 0;
}
static int drm_connector_hdmi_audio_hook_plugged_cb(struct device *dev,
void *data,
hdmi_codec_plugged_cb fn,
struct device *codec_dev)
{
struct drm_connector *connector = data;
mutex_lock(&connector->hdmi_audio.lock);
connector->hdmi_audio.plugged_cb = fn;
connector->hdmi_audio.plugged_cb_dev = codec_dev;
fn(codec_dev, connector->hdmi_audio.last_state);
mutex_unlock(&connector->hdmi_audio.lock);
return 0;
}
void drm_connector_hdmi_audio_plugged_notify(struct drm_connector *connector,
bool plugged)
{
mutex_lock(&connector->hdmi_audio.lock);
connector->hdmi_audio.last_state = plugged;
if (connector->hdmi_audio.plugged_cb &&
connector->hdmi_audio.plugged_cb_dev)
connector->hdmi_audio.plugged_cb(connector->hdmi_audio.plugged_cb_dev,
connector->hdmi_audio.last_state);
mutex_unlock(&connector->hdmi_audio.lock);
}
EXPORT_SYMBOL(drm_connector_hdmi_audio_plugged_notify);
static const struct hdmi_codec_ops drm_connector_hdmi_audio_ops = {
.audio_startup = drm_connector_hdmi_audio_startup,
.prepare = drm_connector_hdmi_audio_prepare,
.audio_shutdown = drm_connector_hdmi_audio_shutdown,
.mute_stream = drm_connector_hdmi_audio_mute_stream,
.get_eld = drm_connector_hdmi_audio_get_eld,
.get_dai_id = drm_connector_hdmi_audio_get_dai_id,
.hook_plugged_cb = drm_connector_hdmi_audio_hook_plugged_cb,
};
/**
* drm_connector_hdmi_audio_init - Initialize HDMI Codec device for the DRM connector
* @connector: A pointer to the connector to allocate codec for
* @hdmi_codec_dev: device to be used as a parent for the HDMI Codec
* @funcs: callbacks for this HDMI Codec
* @max_i2s_playback_channels: maximum number of playback I2S channels
* @spdif_playback: set if HDMI codec has S/PDIF playback port
* @dai_port: sound DAI port, -1 if it is not enabled
*
* Create a HDMI codec device to be used with the specified connector.
*
* Returns:
* Zero on success, error code on failure.
*/
int drm_connector_hdmi_audio_init(struct drm_connector *connector,
struct device *hdmi_codec_dev,
const struct drm_connector_hdmi_audio_funcs *funcs,
unsigned int max_i2s_playback_channels,
bool spdif_playback,
int dai_port)
{
struct hdmi_codec_pdata codec_pdata = {
.ops = &drm_connector_hdmi_audio_ops,
.max_i2s_channels = max_i2s_playback_channels,
.i2s = !!max_i2s_playback_channels,
.spdif = spdif_playback,
.no_i2s_capture = true,
.no_spdif_capture = true,
.data = connector,
};
struct platform_device *pdev;
if (!funcs ||
!funcs->prepare ||
!funcs->shutdown)
return -EINVAL;
connector->hdmi_audio.funcs = funcs;
connector->hdmi_audio.dai_port = dai_port;
pdev = platform_device_register_data(hdmi_codec_dev,
HDMI_CODEC_DRV_NAME,
PLATFORM_DEVID_AUTO,
&codec_pdata, sizeof(codec_pdata));
if (IS_ERR(pdev))
return PTR_ERR(pdev);
connector->hdmi_audio.codec_pdev = pdev;
return 0;
}
EXPORT_SYMBOL(drm_connector_hdmi_audio_init);

View File

@ -5,6 +5,7 @@
#include <drm/drm_edid.h>
#include <drm/drm_print.h>
#include <drm/display/drm_hdmi_audio_helper.h>
#include <drm/display/drm_hdmi_helper.h>
#include <drm/display/drm_hdmi_state_helper.h>
@ -777,3 +778,59 @@ drm_atomic_helper_connector_hdmi_clear_audio_infoframe(struct drm_connector *con
return ret;
}
EXPORT_SYMBOL(drm_atomic_helper_connector_hdmi_clear_audio_infoframe);
static void
drm_atomic_helper_connector_hdmi_update(struct drm_connector *connector,
enum drm_connector_status status)
{
const struct drm_edid *drm_edid;
if (status == connector_status_disconnected) {
// TODO: also handle CEC and scramber, HDMI sink disconnected.
drm_connector_hdmi_audio_plugged_notify(connector, false);
}
if (connector->hdmi.funcs->read_edid)
drm_edid = connector->hdmi.funcs->read_edid(connector);
else
drm_edid = drm_edid_read(connector);
drm_edid_connector_update(connector, drm_edid);
drm_edid_free(drm_edid);
if (status == connector_status_connected) {
// TODO: also handle CEC and scramber, HDMI sink is now connected.
drm_connector_hdmi_audio_plugged_notify(connector, true);
}
}
/**
* drm_atomic_helper_connector_hdmi_hotplug - Handle the hotplug event for the HDMI connector
* @connector: A pointer to the HDMI connector
* @status: Connection status
*
* This function should be called as a part of the .detect() / .detect_ctx()
* callbacks, updating the HDMI-specific connector's data.
*/
void drm_atomic_helper_connector_hdmi_hotplug(struct drm_connector *connector,
enum drm_connector_status status)
{
drm_atomic_helper_connector_hdmi_update(connector, status);
}
EXPORT_SYMBOL(drm_atomic_helper_connector_hdmi_hotplug);
/**
* drm_atomic_helper_connector_hdmi_force - HDMI Connector implementation of the force callback
* @connector: A pointer to the HDMI connector
*
* This function implements the .force() callback for the HDMI connectors. It
* can either be used directly as the callback or should be called from within
* the .force() callback implementation to maintain the HDMI-specific
* connector's data.
*/
void drm_atomic_helper_connector_hdmi_force(struct drm_connector *connector)
{
drm_atomic_helper_connector_hdmi_update(connector, connector->status);
}
EXPORT_SYMBOL(drm_atomic_helper_connector_hdmi_force);

View File

@ -33,6 +33,7 @@
#include <drm/drm_sysfs.h>
#include <drm/drm_utils.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/uaccess.h>
@ -281,6 +282,7 @@ static int drm_connector_init_only(struct drm_device *dev,
mutex_init(&connector->eld_mutex);
mutex_init(&connector->edid_override_mutex);
mutex_init(&connector->hdmi.infoframes.lock);
mutex_init(&connector->hdmi_audio.lock);
connector->edid_blob_ptr = NULL;
connector->epoch_counter = 0;
connector->tile_blob_ptr = NULL;
@ -714,6 +716,8 @@ void drm_connector_cleanup(struct drm_connector *connector)
DRM_CONNECTOR_REGISTERED))
drm_connector_unregister(connector);
platform_device_unregister(connector->hdmi_audio.codec_pdev);
if (connector->privacy_screen) {
drm_privacy_screen_put(connector->privacy_screen);
connector->privacy_screen = NULL;
@ -750,6 +754,7 @@ void drm_connector_cleanup(struct drm_connector *connector)
connector->funcs->atomic_destroy_state(connector,
connector->state);
mutex_destroy(&connector->hdmi_audio.lock);
mutex_destroy(&connector->hdmi.infoframes.lock);
mutex_destroy(&connector->mutex);

View File

@ -845,6 +845,16 @@ static void print_size(struct drm_printer *p, const char *stat,
drm_printf(p, "drm-%s-%s:\t%llu%s\n", stat, region, sz, units[u]);
}
int drm_memory_stats_is_zero(const struct drm_memory_stats *stats)
{
return (stats->shared == 0 &&
stats->private == 0 &&
stats->resident == 0 &&
stats->purgeable == 0 &&
stats->active == 0);
}
EXPORT_SYMBOL(drm_memory_stats_is_zero);
/**
* drm_print_memory_stats - A helper to print memory stats
* @p: The printer to print output to
@ -860,7 +870,9 @@ void drm_print_memory_stats(struct drm_printer *p,
{
print_size(p, "total", region, stats->private + stats->shared);
print_size(p, "shared", region, stats->shared);
print_size(p, "active", region, stats->active);
if (supported_status & DRM_GEM_OBJECT_ACTIVE)
print_size(p, "active", region, stats->active);
if (supported_status & DRM_GEM_OBJECT_RESIDENT)
print_size(p, "resident", region, stats->resident);
@ -893,15 +905,13 @@ void drm_show_memory_stats(struct drm_printer *p, struct drm_file *file)
if (obj->funcs && obj->funcs->status) {
s = obj->funcs->status(obj);
supported_status = DRM_GEM_OBJECT_RESIDENT |
DRM_GEM_OBJECT_PURGEABLE;
supported_status |= s;
}
if (drm_gem_object_is_shared_for_memory_stats(obj)) {
if (drm_gem_object_is_shared_for_memory_stats(obj))
status.shared += obj->size;
} else {
else
status.private += obj->size;
}
if (s & DRM_GEM_OBJECT_RESIDENT) {
status.resident += add_size;
@ -914,6 +924,7 @@ void drm_show_memory_stats(struct drm_printer *p, struct drm_file *file)
if (!dma_resv_test_signaled(obj->resv, dma_resv_usage_rw(true))) {
status.active += add_size;
supported_status |= DRM_GEM_OBJECT_ACTIVE;
/* If still active, don't count as purgeable: */
s &= ~DRM_GEM_OBJECT_PURGEABLE;

View File

@ -414,7 +414,7 @@ bool drm_is_panel_follower(struct device *dev)
* don't bother trying to parse it here. We just need to know if the
* property is there.
*/
return of_property_read_bool(dev->of_node, "panel");
return of_property_present(dev->of_node, "panel");
}
EXPORT_SYMBOL(drm_is_panel_follower);

View File

@ -1660,7 +1660,6 @@ static const struct hdmi_codec_ops audio_codec_ops = {
.audio_shutdown = hdmi_audio_shutdown,
.mute_stream = hdmi_audio_mute,
.get_eld = hdmi_audio_get_eld,
.no_capture_mute = 1,
};
static int hdmi_register_audio_device(struct hdmi_context *hdata)
@ -1669,6 +1668,7 @@ static int hdmi_register_audio_device(struct hdmi_context *hdata)
.ops = &audio_codec_ops,
.max_i2s_channels = 6,
.i2s = 1,
.no_capture_mute = 1,
};
hdata->audio.pdev = platform_device_register_data(

View File

@ -1,4 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-only
hibmc-drm-y := hibmc_drm_drv.o hibmc_drm_de.o hibmc_drm_vdac.o hibmc_drm_i2c.o
hibmc-drm-y := hibmc_drm_drv.o hibmc_drm_de.o hibmc_drm_vdac.o hibmc_drm_i2c.o \
dp/dp_aux.o dp/dp_link.o dp/dp_hw.o hibmc_drm_dp.o
obj-$(CONFIG_DRM_HISI_HIBMC) += hibmc-drm.o

View File

@ -0,0 +1,164 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2024 Hisilicon Limited.
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/minmax.h>
#include <drm/drm_device.h>
#include <drm/drm_print.h>
#include "dp_comm.h"
#include "dp_reg.h"
#define HIBMC_AUX_CMD_REQ_LEN GENMASK(7, 4)
#define HIBMC_AUX_CMD_ADDR GENMASK(27, 8)
#define HIBMC_AUX_CMD_I2C_ADDR_ONLY BIT(28)
#define HIBMC_BYTES_IN_U32 4
#define HIBMC_AUX_I2C_WRITE_SUCCESS 0x1
#define HIBMC_DP_MIN_PULSE_NUM 0x9
#define BITS_IN_U8 8
static inline void hibmc_dp_aux_reset(struct hibmc_dp_dev *dp)
{
hibmc_dp_reg_write_field(dp, HIBMC_DP_DPTX_RST_CTRL, HIBMC_DP_CFG_AUX_RST_N, 0x0);
usleep_range(10, 15);
hibmc_dp_reg_write_field(dp, HIBMC_DP_DPTX_RST_CTRL, HIBMC_DP_CFG_AUX_RST_N, 0x1);
}
static void hibmc_dp_aux_read_data(struct hibmc_dp_dev *dp, u8 *buf, u8 size)
{
u32 reg_num;
u32 value;
u32 num;
u8 i, j;
reg_num = DIV_ROUND_UP(size, HIBMC_BYTES_IN_U32);
for (i = 0; i < reg_num; i++) {
/* number of bytes read from a single register */
num = min(size - i * HIBMC_BYTES_IN_U32, HIBMC_BYTES_IN_U32);
value = readl(dp->base + HIBMC_DP_AUX_RD_DATA0 + i * HIBMC_BYTES_IN_U32);
/* convert the 32-bit value of the register to the buffer. */
for (j = 0; j < num; j++)
buf[i * HIBMC_BYTES_IN_U32 + j] = value >> (j * BITS_IN_U8);
}
}
static void hibmc_dp_aux_write_data(struct hibmc_dp_dev *dp, u8 *buf, u8 size)
{
u32 reg_num;
u32 value;
u32 num;
u8 i, j;
reg_num = DIV_ROUND_UP(size, HIBMC_BYTES_IN_U32);
for (i = 0; i < reg_num; i++) {
/* number of bytes written to a single register */
num = min_t(u8, size - i * HIBMC_BYTES_IN_U32, HIBMC_BYTES_IN_U32);
value = 0;
/* obtain the 32-bit value written to a single register. */
for (j = 0; j < num; j++)
value |= buf[i * HIBMC_BYTES_IN_U32 + j] << (j * BITS_IN_U8);
/* writing data to a single register */
writel(value, dp->base + HIBMC_DP_AUX_WR_DATA0 + i * HIBMC_BYTES_IN_U32);
}
}
static u32 hibmc_dp_aux_build_cmd(const struct drm_dp_aux_msg *msg)
{
u32 aux_cmd = msg->request;
if (msg->size)
aux_cmd |= FIELD_PREP(HIBMC_AUX_CMD_REQ_LEN, (msg->size - 1));
else
aux_cmd |= FIELD_PREP(HIBMC_AUX_CMD_I2C_ADDR_ONLY, 1);
aux_cmd |= FIELD_PREP(HIBMC_AUX_CMD_ADDR, msg->address);
return aux_cmd;
}
/* ret >= 0, ret is size; ret < 0, ret is err code */
static int hibmc_dp_aux_parse_xfer(struct hibmc_dp_dev *dp, struct drm_dp_aux_msg *msg)
{
u32 buf_data_cnt;
u32 aux_status;
aux_status = readl(dp->base + HIBMC_DP_AUX_STATUS);
msg->reply = FIELD_GET(HIBMC_DP_CFG_AUX_STATUS, aux_status);
if (aux_status & HIBMC_DP_CFG_AUX_TIMEOUT)
return -ETIMEDOUT;
/* only address */
if (!msg->size)
return 0;
if (msg->reply != DP_AUX_NATIVE_REPLY_ACK)
return -EIO;
buf_data_cnt = FIELD_GET(HIBMC_DP_CFG_AUX_READY_DATA_BYTE, aux_status);
switch (msg->request) {
case DP_AUX_NATIVE_WRITE:
return msg->size;
case DP_AUX_I2C_WRITE | DP_AUX_I2C_MOT:
if (buf_data_cnt == HIBMC_AUX_I2C_WRITE_SUCCESS)
return msg->size;
else
return FIELD_GET(HIBMC_DP_CFG_AUX, aux_status);
case DP_AUX_NATIVE_READ:
case DP_AUX_I2C_READ | DP_AUX_I2C_MOT:
buf_data_cnt--;
if (buf_data_cnt != msg->size) {
/* only the successful part of data is read */
return -EBUSY;
}
/* all data is successfully read */
hibmc_dp_aux_read_data(dp, msg->buffer, msg->size);
return msg->size;
default:
return -EINVAL;
}
}
/* ret >= 0 ,ret is size; ret < 0, ret is err code */
static ssize_t hibmc_dp_aux_xfer(struct drm_dp_aux *aux, struct drm_dp_aux_msg *msg)
{
struct hibmc_dp_dev *dp = container_of(aux, struct hibmc_dp_dev, aux);
u32 aux_cmd;
int ret;
u32 val; /* val will be assigned at the beginning of readl_poll_timeout function */
writel(0, dp->base + HIBMC_DP_AUX_WR_DATA0);
writel(0, dp->base + HIBMC_DP_AUX_WR_DATA1);
writel(0, dp->base + HIBMC_DP_AUX_WR_DATA2);
writel(0, dp->base + HIBMC_DP_AUX_WR_DATA3);
hibmc_dp_aux_write_data(dp, msg->buffer, msg->size);
aux_cmd = hibmc_dp_aux_build_cmd(msg);
writel(aux_cmd, dp->base + HIBMC_DP_AUX_CMD_ADDR);
/* enable aux transfer */
hibmc_dp_reg_write_field(dp, HIBMC_DP_AUX_REQ, HIBMC_DP_CFG_AUX_REQ, 0x1);
ret = readl_poll_timeout(dp->base + HIBMC_DP_AUX_REQ, val,
!(val & HIBMC_DP_CFG_AUX_REQ), 50, 5000);
if (ret) {
hibmc_dp_aux_reset(dp);
return ret;
}
return hibmc_dp_aux_parse_xfer(dp, msg);
}
void hibmc_dp_aux_init(struct hibmc_dp_dev *dp)
{
hibmc_dp_reg_write_field(dp, HIBMC_DP_AUX_REQ, HIBMC_DP_CFG_AUX_SYNC_LEN_SEL, 0x0);
hibmc_dp_reg_write_field(dp, HIBMC_DP_AUX_REQ, HIBMC_DP_CFG_AUX_TIMER_TIMEOUT, 0x1);
hibmc_dp_reg_write_field(dp, HIBMC_DP_AUX_REQ, HIBMC_DP_CFG_AUX_MIN_PULSE_NUM,
HIBMC_DP_MIN_PULSE_NUM);
dp->aux.transfer = hibmc_dp_aux_xfer;
dp->aux.is_remote = 0;
drm_dp_aux_init(&dp->aux);
}

View File

@ -0,0 +1,63 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/* Copyright (c) 2024 Hisilicon Limited. */
#ifndef DP_COMM_H
#define DP_COMM_H
#include <linux/types.h>
#include <linux/bitops.h>
#include <linux/errno.h>
#include <linux/mutex.h>
#include <linux/kernel.h>
#include <linux/bitfield.h>
#include <linux/io.h>
#include <drm/display/drm_dp_helper.h>
#define HIBMC_DP_LANE_NUM_MAX 2
struct hibmc_link_status {
bool clock_recovered;
bool channel_equalized;
};
struct hibmc_link_cap {
u8 link_rate;
u8 lanes;
};
struct hibmc_dp_link {
struct hibmc_link_status status;
u8 train_set[HIBMC_DP_LANE_NUM_MAX];
struct hibmc_link_cap cap;
};
struct hibmc_dp_dev {
struct drm_dp_aux aux;
struct drm_device *dev;
void __iomem *base;
struct mutex lock; /* protects concurrent RW in hibmc_dp_reg_write_field() */
struct hibmc_dp_link link;
u8 dpcd[DP_RECEIVER_CAP_SIZE];
};
#define dp_field_modify(reg_value, mask, val) \
do { \
(reg_value) &= ~(mask); \
(reg_value) |= FIELD_PREP(mask, val); \
} while (0) \
#define hibmc_dp_reg_write_field(dp, offset, mask, val) \
do { \
typeof(dp) _dp = dp; \
typeof(_dp->base) addr = (_dp->base + (offset)); \
mutex_lock(&_dp->lock); \
u32 reg_value = readl(addr); \
dp_field_modify(reg_value, mask, val); \
writel(reg_value, addr); \
mutex_unlock(&_dp->lock); \
} while (0)
void hibmc_dp_aux_init(struct hibmc_dp_dev *dp);
int hibmc_dp_link_training(struct hibmc_dp_dev *dp);
#endif

View File

@ -0,0 +1,19 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/* Copyright (c) 2024 Hisilicon Limited. */
#ifndef DP_CONFIG_H
#define DP_CONFIG_H
#define HIBMC_DP_BPP 24
#define HIBMC_DP_SYMBOL_PER_FCLK 4
#define HIBMC_DP_MSA1 0x20
#define HIBMC_DP_MSA2 0x845c00
#define HIBMC_DP_OFFSET 0x1e0000
#define HIBMC_DP_HDCP 0x2
#define HIBMC_DP_INT_RST 0xffff
#define HIBMC_DP_DPTX_RST 0x3ff
#define HIBMC_DP_CLK_EN 0x7
#define HIBMC_DP_SYNC_EN_MASK 0x3
#define HIBMC_DP_LINK_RATE_CAL 27
#endif

View File

@ -0,0 +1,220 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2024 Hisilicon Limited.
#include <linux/io.h>
#include <linux/delay.h>
#include "dp_config.h"
#include "dp_comm.h"
#include "dp_reg.h"
#include "dp_hw.h"
static void hibmc_dp_set_tu(struct hibmc_dp_dev *dp, struct drm_display_mode *mode)
{
u32 tu_symbol_frac_size;
u32 tu_symbol_size;
u32 rate_ks;
u8 lane_num;
u32 value;
u32 bpp;
lane_num = dp->link.cap.lanes;
if (lane_num == 0) {
drm_err(dp->dev, "set tu failed, lane num cannot be 0!\n");
return;
}
bpp = HIBMC_DP_BPP;
rate_ks = dp->link.cap.link_rate * HIBMC_DP_LINK_RATE_CAL;
value = (mode->clock * bpp * 5) / (61 * lane_num * rate_ks);
if (value % 10 == 9) { /* 9 carry */
tu_symbol_size = value / 10 + 1;
tu_symbol_frac_size = 0;
} else {
tu_symbol_size = value / 10;
tu_symbol_frac_size = value % 10 + 1;
}
drm_dbg_dp(dp->dev, "tu value: %u.%u value: %u\n",
tu_symbol_size, tu_symbol_frac_size, value);
hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_PACKET,
HIBMC_DP_CFG_STREAM_TU_SYMBOL_SIZE, tu_symbol_size);
hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_PACKET,
HIBMC_DP_CFG_STREAM_TU_SYMBOL_FRAC_SIZE, tu_symbol_frac_size);
}
static void hibmc_dp_set_sst(struct hibmc_dp_dev *dp, struct drm_display_mode *mode)
{
u32 hblank_size;
u32 htotal_size;
u32 htotal_int;
u32 hblank_int;
u32 fclk; /* flink_clock */
fclk = dp->link.cap.link_rate * HIBMC_DP_LINK_RATE_CAL;
/* Considering the effect of spread spectrum, the value may be deviated.
* The coefficient (0.9947) is used to offset the deviation.
*/
htotal_int = mode->htotal * 9947 / 10000;
htotal_size = htotal_int * fclk / (HIBMC_DP_SYMBOL_PER_FCLK * (mode->clock / 1000));
hblank_int = mode->htotal - mode->hdisplay - mode->hdisplay * 53 / 10000;
hblank_size = hblank_int * fclk * 9947 /
(mode->clock * 10 * HIBMC_DP_SYMBOL_PER_FCLK);
drm_dbg_dp(dp->dev, "h_active %u v_active %u htotal_size %u hblank_size %u",
mode->hdisplay, mode->vdisplay, htotal_size, hblank_size);
drm_dbg_dp(dp->dev, "flink_clock %u pixel_clock %d", fclk, mode->clock / 1000);
hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_HORIZONTAL_SIZE,
HIBMC_DP_CFG_STREAM_HTOTAL_SIZE, htotal_size);
hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_HORIZONTAL_SIZE,
HIBMC_DP_CFG_STREAM_HBLANK_SIZE, hblank_size);
}
static void hibmc_dp_link_cfg(struct hibmc_dp_dev *dp, struct drm_display_mode *mode)
{
u32 timing_delay;
u32 vblank;
u32 hstart;
u32 vstart;
vblank = mode->vtotal - mode->vdisplay;
timing_delay = mode->htotal - mode->hsync_start;
hstart = mode->htotal - mode->hsync_start;
vstart = mode->vtotal - mode->vsync_start;
hibmc_dp_reg_write_field(dp, HIBMC_DP_TIMING_GEN_CONFIG0,
HIBMC_DP_CFG_TIMING_GEN0_HBLANK, mode->htotal - mode->hdisplay);
hibmc_dp_reg_write_field(dp, HIBMC_DP_TIMING_GEN_CONFIG0,
HIBMC_DP_CFG_TIMING_GEN0_HACTIVE, mode->hdisplay);
hibmc_dp_reg_write_field(dp, HIBMC_DP_TIMING_GEN_CONFIG2,
HIBMC_DP_CFG_TIMING_GEN0_VBLANK, vblank);
hibmc_dp_reg_write_field(dp, HIBMC_DP_TIMING_GEN_CONFIG2,
HIBMC_DP_CFG_TIMING_GEN0_VACTIVE, mode->vdisplay);
hibmc_dp_reg_write_field(dp, HIBMC_DP_TIMING_GEN_CONFIG3,
HIBMC_DP_CFG_TIMING_GEN0_VFRONT_PORCH,
mode->vsync_start - mode->vdisplay);
hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_CONFIG0,
HIBMC_DP_CFG_STREAM_HACTIVE, mode->hdisplay);
hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_CONFIG0,
HIBMC_DP_CFG_STREAM_HBLANK, mode->htotal - mode->hdisplay);
hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_CONFIG2,
HIBMC_DP_CFG_STREAM_HSYNC_WIDTH,
mode->hsync_end - mode->hsync_start);
hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_CONFIG1,
HIBMC_DP_CFG_STREAM_VACTIVE, mode->vdisplay);
hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_CONFIG1,
HIBMC_DP_CFG_STREAM_VBLANK, vblank);
hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_CONFIG3,
HIBMC_DP_CFG_STREAM_VFRONT_PORCH,
mode->vsync_start - mode->vdisplay);
hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_CONFIG3,
HIBMC_DP_CFG_STREAM_VSYNC_WIDTH,
mode->vsync_end - mode->vsync_start);
hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_MSA0,
HIBMC_DP_CFG_STREAM_VSTART, vstart);
hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_MSA0,
HIBMC_DP_CFG_STREAM_HSTART, hstart);
hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_CTRL, HIBMC_DP_CFG_STREAM_VSYNC_POLARITY,
mode->flags & DRM_MODE_FLAG_PVSYNC ? 1 : 0);
hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_CTRL, HIBMC_DP_CFG_STREAM_HSYNC_POLARITY,
mode->flags & DRM_MODE_FLAG_PHSYNC ? 1 : 0);
/* MSA mic 0 and 1 */
writel(HIBMC_DP_MSA1, dp->base + HIBMC_DP_VIDEO_MSA1);
writel(HIBMC_DP_MSA2, dp->base + HIBMC_DP_VIDEO_MSA2);
hibmc_dp_set_tu(dp, mode);
hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_CTRL, HIBMC_DP_CFG_STREAM_RGB_ENABLE, 0x1);
hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_CTRL, HIBMC_DP_CFG_STREAM_VIDEO_MAPPING, 0);
/* divide 2: up even */
if (timing_delay % 2)
timing_delay++;
hibmc_dp_reg_write_field(dp, HIBMC_DP_TIMING_MODEL_CTRL,
HIBMC_DP_CFG_PIXEL_NUM_TIMING_MODE_SEL1, timing_delay);
hibmc_dp_set_sst(dp, mode);
}
int hibmc_dp_hw_init(struct hibmc_dp *dp)
{
struct drm_device *drm_dev = dp->drm_dev;
struct hibmc_dp_dev *dp_dev;
dp_dev = devm_kzalloc(drm_dev->dev, sizeof(struct hibmc_dp_dev), GFP_KERNEL);
if (!dp_dev)
return -ENOMEM;
mutex_init(&dp_dev->lock);
dp->dp_dev = dp_dev;
dp_dev->dev = drm_dev;
dp_dev->base = dp->mmio + HIBMC_DP_OFFSET;
hibmc_dp_aux_init(dp_dev);
dp_dev->link.cap.lanes = 0x2;
dp_dev->link.cap.link_rate = DP_LINK_BW_2_7;
/* hdcp data */
writel(HIBMC_DP_HDCP, dp_dev->base + HIBMC_DP_HDCP_CFG);
/* int init */
writel(0, dp_dev->base + HIBMC_DP_INTR_ENABLE);
writel(HIBMC_DP_INT_RST, dp_dev->base + HIBMC_DP_INTR_ORIGINAL_STATUS);
/* rst */
writel(HIBMC_DP_DPTX_RST, dp_dev->base + HIBMC_DP_DPTX_RST_CTRL);
/* clock enable */
writel(HIBMC_DP_CLK_EN, dp_dev->base + HIBMC_DP_DPTX_CLK_CTRL);
return 0;
}
void hibmc_dp_display_en(struct hibmc_dp *dp, bool enable)
{
struct hibmc_dp_dev *dp_dev = dp->dp_dev;
if (enable) {
hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_VIDEO_CTRL, BIT(0), 0x1);
writel(HIBMC_DP_SYNC_EN_MASK, dp_dev->base + HIBMC_DP_TIMING_SYNC_CTRL);
hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_DPTX_GCTL0, BIT(10), 0x1);
writel(HIBMC_DP_SYNC_EN_MASK, dp_dev->base + HIBMC_DP_TIMING_SYNC_CTRL);
} else {
hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_DPTX_GCTL0, BIT(10), 0);
writel(HIBMC_DP_SYNC_EN_MASK, dp_dev->base + HIBMC_DP_TIMING_SYNC_CTRL);
hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_VIDEO_CTRL, BIT(0), 0);
writel(HIBMC_DP_SYNC_EN_MASK, dp_dev->base + HIBMC_DP_TIMING_SYNC_CTRL);
}
msleep(50);
}
int hibmc_dp_mode_set(struct hibmc_dp *dp, struct drm_display_mode *mode)
{
struct hibmc_dp_dev *dp_dev = dp->dp_dev;
int ret;
if (!dp_dev->link.status.channel_equalized) {
ret = hibmc_dp_link_training(dp_dev);
if (ret) {
drm_err(dp->drm_dev, "dp link training failed, ret: %d\n", ret);
return ret;
}
}
hibmc_dp_display_en(dp, false);
hibmc_dp_link_cfg(dp_dev, mode);
return 0;
}

View File

@ -0,0 +1,28 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/* Copyright (c) 2024 Hisilicon Limited. */
#ifndef DP_KAPI_H
#define DP_KAPI_H
#include <linux/types.h>
#include <linux/delay.h>
#include <drm/drm_device.h>
#include <drm/drm_encoder.h>
#include <drm/drm_connector.h>
#include <drm/drm_print.h>
struct hibmc_dp_dev;
struct hibmc_dp {
struct hibmc_dp_dev *dp_dev;
struct drm_device *drm_dev;
struct drm_encoder encoder;
struct drm_connector connector;
void __iomem *mmio;
};
int hibmc_dp_hw_init(struct hibmc_dp *dp);
int hibmc_dp_mode_set(struct hibmc_dp *dp, struct drm_display_mode *mode);
void hibmc_dp_display_en(struct hibmc_dp *dp, bool enable);
#endif

View File

@ -0,0 +1,332 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2024 Hisilicon Limited.
#include <linux/delay.h>
#include <drm/drm_device.h>
#include <drm/drm_print.h>
#include "dp_comm.h"
#include "dp_reg.h"
#define HIBMC_EQ_MAX_RETRY 5
static int hibmc_dp_link_training_configure(struct hibmc_dp_dev *dp)
{
u8 buf[2];
int ret;
/* DP 2 lane */
hibmc_dp_reg_write_field(dp, HIBMC_DP_PHYIF_CTRL0, HIBMC_DP_CFG_LANE_DATA_EN,
dp->link.cap.lanes == 0x2 ? 0x3 : 0x1);
hibmc_dp_reg_write_field(dp, HIBMC_DP_DPTX_GCTL0, HIBMC_DP_CFG_PHY_LANE_NUM,
dp->link.cap.lanes == 0x2 ? 0x1 : 0);
/* enhanced frame */
hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_CTRL, HIBMC_DP_CFG_STREAM_FRAME_MODE, 0x1);
/* set rate and lane count */
buf[0] = dp->link.cap.link_rate;
buf[1] = DP_LANE_COUNT_ENHANCED_FRAME_EN | dp->link.cap.lanes;
ret = drm_dp_dpcd_write(&dp->aux, DP_LINK_BW_SET, buf, sizeof(buf));
if (ret != sizeof(buf)) {
drm_dbg_dp(dp->dev, "dp aux write link rate and lanes failed, ret: %d\n", ret);
return ret >= 0 ? -EIO : ret;
}
/* set 8b/10b and downspread */
buf[0] = DP_SPREAD_AMP_0_5;
buf[1] = DP_SET_ANSI_8B10B;
ret = drm_dp_dpcd_write(&dp->aux, DP_DOWNSPREAD_CTRL, buf, sizeof(buf));
if (ret != sizeof(buf)) {
drm_dbg_dp(dp->dev, "dp aux write 8b/10b and downspread failed, ret: %d\n", ret);
return ret >= 0 ? -EIO : ret;
}
ret = drm_dp_read_dpcd_caps(&dp->aux, dp->dpcd);
if (ret)
drm_err(dp->dev, "dp aux read dpcd failed, ret: %d\n", ret);
return ret;
}
static int hibmc_dp_link_set_pattern(struct hibmc_dp_dev *dp, int pattern)
{
int ret;
u8 val;
u8 buf;
buf = (u8)pattern;
if (pattern != DP_TRAINING_PATTERN_DISABLE && pattern != DP_TRAINING_PATTERN_4) {
buf |= DP_LINK_SCRAMBLING_DISABLE;
hibmc_dp_reg_write_field(dp, HIBMC_DP_PHYIF_CTRL0, HIBMC_DP_CFG_SCRAMBLE_EN, 0x1);
} else {
hibmc_dp_reg_write_field(dp, HIBMC_DP_PHYIF_CTRL0, HIBMC_DP_CFG_SCRAMBLE_EN, 0);
}
switch (pattern) {
case DP_TRAINING_PATTERN_DISABLE:
val = 0;
break;
case DP_TRAINING_PATTERN_1:
val = 1;
break;
case DP_TRAINING_PATTERN_2:
val = 2;
break;
case DP_TRAINING_PATTERN_3:
val = 3;
break;
case DP_TRAINING_PATTERN_4:
val = 4;
break;
default:
return -EINVAL;
}
hibmc_dp_reg_write_field(dp, HIBMC_DP_PHYIF_CTRL0, HIBMC_DP_CFG_PAT_SEL, val);
ret = drm_dp_dpcd_write(&dp->aux, DP_TRAINING_PATTERN_SET, &buf, sizeof(buf));
if (ret != sizeof(buf)) {
drm_dbg_dp(dp->dev, "dp aux write training pattern set failed\n");
return ret >= 0 ? -EIO : ret;
}
return 0;
}
static int hibmc_dp_link_training_cr_pre(struct hibmc_dp_dev *dp)
{
u8 *train_set = dp->link.train_set;
int ret;
u8 i;
ret = hibmc_dp_link_training_configure(dp);
if (ret)
return ret;
ret = hibmc_dp_link_set_pattern(dp, DP_TRAINING_PATTERN_1);
if (ret)
return ret;
for (i = 0; i < dp->link.cap.lanes; i++)
train_set[i] = DP_TRAIN_VOLTAGE_SWING_LEVEL_2;
ret = drm_dp_dpcd_write(&dp->aux, DP_TRAINING_LANE0_SET, train_set, dp->link.cap.lanes);
if (ret != dp->link.cap.lanes) {
drm_dbg_dp(dp->dev, "dp aux write training lane set failed\n");
return ret >= 0 ? -EIO : ret;
}
return 0;
}
static bool hibmc_dp_link_get_adjust_train(struct hibmc_dp_dev *dp,
u8 lane_status[DP_LINK_STATUS_SIZE])
{
u8 train_set[HIBMC_DP_LANE_NUM_MAX] = {0};
u8 lane;
for (lane = 0; lane < dp->link.cap.lanes; lane++)
train_set[lane] = drm_dp_get_adjust_request_voltage(lane_status, lane) |
drm_dp_get_adjust_request_pre_emphasis(lane_status, lane);
if (memcmp(dp->link.train_set, train_set, HIBMC_DP_LANE_NUM_MAX)) {
memcpy(dp->link.train_set, train_set, HIBMC_DP_LANE_NUM_MAX);
return true;
}
return false;
}
static inline int hibmc_dp_link_reduce_rate(struct hibmc_dp_dev *dp)
{
switch (dp->link.cap.link_rate) {
case DP_LINK_BW_2_7:
dp->link.cap.link_rate = DP_LINK_BW_1_62;
return 0;
case DP_LINK_BW_5_4:
dp->link.cap.link_rate = DP_LINK_BW_2_7;
return 0;
case DP_LINK_BW_8_1:
dp->link.cap.link_rate = DP_LINK_BW_5_4;
return 0;
default:
return -EINVAL;
}
}
static inline int hibmc_dp_link_reduce_lane(struct hibmc_dp_dev *dp)
{
switch (dp->link.cap.lanes) {
case 0x2:
dp->link.cap.lanes--;
break;
case 0x1:
drm_err(dp->dev, "dp link training reduce lane failed, already reach minimum\n");
return -EIO;
default:
return -EINVAL;
}
return 0;
}
static int hibmc_dp_link_training_cr(struct hibmc_dp_dev *dp)
{
u8 lane_status[DP_LINK_STATUS_SIZE] = {0};
bool level_changed;
u32 voltage_tries;
u32 cr_tries;
int ret;
/*
* DP 1.4 spec define 10 for maxtries value, for pre DP 1.4 version set a limit of 80
* (4 voltage levels x 4 preemphasis levels x 5 identical voltage retries)
*/
voltage_tries = 1;
for (cr_tries = 0; cr_tries < 80; cr_tries++) {
drm_dp_link_train_clock_recovery_delay(&dp->aux, dp->dpcd);
ret = drm_dp_dpcd_read_link_status(&dp->aux, lane_status);
if (ret != DP_LINK_STATUS_SIZE) {
drm_err(dp->dev, "Get lane status failed\n");
return ret;
}
if (drm_dp_clock_recovery_ok(lane_status, dp->link.cap.lanes)) {
drm_dbg_dp(dp->dev, "dp link training cr done\n");
dp->link.status.clock_recovered = true;
return 0;
}
if (voltage_tries == 5) {
drm_dbg_dp(dp->dev, "same voltage tries 5 times\n");
dp->link.status.clock_recovered = false;
return 0;
}
level_changed = hibmc_dp_link_get_adjust_train(dp, lane_status);
ret = drm_dp_dpcd_write(&dp->aux, DP_TRAINING_LANE0_SET, dp->link.train_set,
dp->link.cap.lanes);
if (ret != dp->link.cap.lanes) {
drm_dbg_dp(dp->dev, "Update link training failed\n");
return ret >= 0 ? -EIO : ret;
}
voltage_tries = level_changed ? 1 : voltage_tries + 1;
}
drm_err(dp->dev, "dp link training clock recovery 80 times failed\n");
dp->link.status.clock_recovered = false;
return 0;
}
static int hibmc_dp_link_training_channel_eq(struct hibmc_dp_dev *dp)
{
u8 lane_status[DP_LINK_STATUS_SIZE] = {0};
u8 eq_tries;
int ret;
ret = hibmc_dp_link_set_pattern(dp, DP_TRAINING_PATTERN_2);
if (ret)
return ret;
for (eq_tries = 0; eq_tries < HIBMC_EQ_MAX_RETRY; eq_tries++) {
drm_dp_link_train_channel_eq_delay(&dp->aux, dp->dpcd);
ret = drm_dp_dpcd_read_link_status(&dp->aux, lane_status);
if (ret != DP_LINK_STATUS_SIZE) {
drm_err(dp->dev, "get lane status failed\n");
break;
}
if (!drm_dp_clock_recovery_ok(lane_status, dp->link.cap.lanes)) {
drm_dbg_dp(dp->dev, "clock recovery check failed\n");
drm_dbg_dp(dp->dev, "cannot continue channel equalization\n");
dp->link.status.clock_recovered = false;
break;
}
if (drm_dp_channel_eq_ok(lane_status, dp->link.cap.lanes)) {
dp->link.status.channel_equalized = true;
drm_dbg_dp(dp->dev, "dp link training eq done\n");
break;
}
hibmc_dp_link_get_adjust_train(dp, lane_status);
ret = drm_dp_dpcd_write(&dp->aux, DP_TRAINING_LANE0_SET,
dp->link.train_set, dp->link.cap.lanes);
if (ret != dp->link.cap.lanes) {
drm_dbg_dp(dp->dev, "Update link training failed\n");
ret = (ret >= 0) ? -EIO : ret;
break;
}
}
if (eq_tries == HIBMC_EQ_MAX_RETRY)
drm_err(dp->dev, "channel equalization failed %u times\n", eq_tries);
hibmc_dp_link_set_pattern(dp, DP_TRAINING_PATTERN_DISABLE);
return ret < 0 ? ret : 0;
}
static int hibmc_dp_link_downgrade_training_cr(struct hibmc_dp_dev *dp)
{
if (hibmc_dp_link_reduce_rate(dp))
return hibmc_dp_link_reduce_lane(dp);
return 0;
}
static int hibmc_dp_link_downgrade_training_eq(struct hibmc_dp_dev *dp)
{
if ((dp->link.status.clock_recovered && !dp->link.status.channel_equalized)) {
if (!hibmc_dp_link_reduce_lane(dp))
return 0;
}
return hibmc_dp_link_reduce_rate(dp);
}
int hibmc_dp_link_training(struct hibmc_dp_dev *dp)
{
struct hibmc_dp_link *link = &dp->link;
int ret;
while (true) {
ret = hibmc_dp_link_training_cr_pre(dp);
if (ret)
goto err;
ret = hibmc_dp_link_training_cr(dp);
if (ret)
goto err;
if (!link->status.clock_recovered) {
ret = hibmc_dp_link_downgrade_training_cr(dp);
if (ret)
goto err;
continue;
}
ret = hibmc_dp_link_training_channel_eq(dp);
if (ret)
goto err;
if (!link->status.channel_equalized) {
ret = hibmc_dp_link_downgrade_training_eq(dp);
if (ret)
goto err;
continue;
}
return 0;
}
err:
hibmc_dp_link_set_pattern(dp, DP_TRAINING_PATTERN_DISABLE);
return ret;
}

View File

@ -0,0 +1,76 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/* Copyright (c) 2024 Hisilicon Limited. */
#ifndef DP_REG_H
#define DP_REG_H
#define HIBMC_DP_AUX_CMD_ADDR 0x50
#define HIBMC_DP_AUX_WR_DATA0 0x54
#define HIBMC_DP_AUX_WR_DATA1 0x58
#define HIBMC_DP_AUX_WR_DATA2 0x5c
#define HIBMC_DP_AUX_WR_DATA3 0x60
#define HIBMC_DP_AUX_RD_DATA0 0x64
#define HIBMC_DP_AUX_REQ 0x74
#define HIBMC_DP_AUX_STATUS 0x78
#define HIBMC_DP_PHYIF_CTRL0 0xa0
#define HIBMC_DP_VIDEO_CTRL 0x100
#define HIBMC_DP_VIDEO_CONFIG0 0x104
#define HIBMC_DP_VIDEO_CONFIG1 0x108
#define HIBMC_DP_VIDEO_CONFIG2 0x10c
#define HIBMC_DP_VIDEO_CONFIG3 0x110
#define HIBMC_DP_VIDEO_PACKET 0x114
#define HIBMC_DP_VIDEO_MSA0 0x118
#define HIBMC_DP_VIDEO_MSA1 0x11c
#define HIBMC_DP_VIDEO_MSA2 0x120
#define HIBMC_DP_VIDEO_HORIZONTAL_SIZE 0X124
#define HIBMC_DP_TIMING_GEN_CONFIG0 0x26c
#define HIBMC_DP_TIMING_GEN_CONFIG2 0x274
#define HIBMC_DP_TIMING_GEN_CONFIG3 0x278
#define HIBMC_DP_HDCP_CFG 0x600
#define HIBMC_DP_DPTX_RST_CTRL 0x700
#define HIBMC_DP_DPTX_CLK_CTRL 0x704
#define HIBMC_DP_DPTX_GCTL0 0x708
#define HIBMC_DP_INTR_ENABLE 0x720
#define HIBMC_DP_INTR_ORIGINAL_STATUS 0x728
#define HIBMC_DP_TIMING_MODEL_CTRL 0x884
#define HIBMC_DP_TIMING_SYNC_CTRL 0xFF0
#define HIBMC_DP_CFG_AUX_SYNC_LEN_SEL BIT(1)
#define HIBMC_DP_CFG_AUX_TIMER_TIMEOUT BIT(2)
#define HIBMC_DP_CFG_STREAM_FRAME_MODE BIT(6)
#define HIBMC_DP_CFG_AUX_MIN_PULSE_NUM GENMASK(13, 9)
#define HIBMC_DP_CFG_LANE_DATA_EN GENMASK(11, 8)
#define HIBMC_DP_CFG_PHY_LANE_NUM GENMASK(2, 1)
#define HIBMC_DP_CFG_AUX_REQ BIT(0)
#define HIBMC_DP_CFG_AUX_RST_N BIT(4)
#define HIBMC_DP_CFG_AUX_TIMEOUT BIT(0)
#define HIBMC_DP_CFG_AUX_READY_DATA_BYTE GENMASK(16, 12)
#define HIBMC_DP_CFG_AUX GENMASK(24, 17)
#define HIBMC_DP_CFG_AUX_STATUS GENMASK(11, 4)
#define HIBMC_DP_CFG_SCRAMBLE_EN BIT(0)
#define HIBMC_DP_CFG_PAT_SEL GENMASK(7, 4)
#define HIBMC_DP_CFG_TIMING_GEN0_HACTIVE GENMASK(31, 16)
#define HIBMC_DP_CFG_TIMING_GEN0_HBLANK GENMASK(15, 0)
#define HIBMC_DP_CFG_TIMING_GEN0_VACTIVE GENMASK(31, 16)
#define HIBMC_DP_CFG_TIMING_GEN0_VBLANK GENMASK(15, 0)
#define HIBMC_DP_CFG_TIMING_GEN0_VFRONT_PORCH GENMASK(31, 16)
#define HIBMC_DP_CFG_STREAM_HACTIVE GENMASK(31, 16)
#define HIBMC_DP_CFG_STREAM_HBLANK GENMASK(15, 0)
#define HIBMC_DP_CFG_STREAM_HSYNC_WIDTH GENMASK(15, 0)
#define HIBMC_DP_CFG_STREAM_VACTIVE GENMASK(31, 16)
#define HIBMC_DP_CFG_STREAM_VBLANK GENMASK(15, 0)
#define HIBMC_DP_CFG_STREAM_VFRONT_PORCH GENMASK(31, 16)
#define HIBMC_DP_CFG_STREAM_VSYNC_WIDTH GENMASK(15, 0)
#define HIBMC_DP_CFG_STREAM_VSTART GENMASK(31, 16)
#define HIBMC_DP_CFG_STREAM_HSTART GENMASK(15, 0)
#define HIBMC_DP_CFG_STREAM_VSYNC_POLARITY BIT(8)
#define HIBMC_DP_CFG_STREAM_HSYNC_POLARITY BIT(7)
#define HIBMC_DP_CFG_STREAM_RGB_ENABLE BIT(1)
#define HIBMC_DP_CFG_STREAM_VIDEO_MAPPING GENMASK(5, 2)
#define HIBMC_DP_CFG_PIXEL_NUM_TIMING_MODE_SEL1 GENMASK(31, 16)
#define HIBMC_DP_CFG_STREAM_TU_SYMBOL_SIZE GENMASK(5, 0)
#define HIBMC_DP_CFG_STREAM_TU_SYMBOL_FRAC_SIZE GENMASK(9, 6)
#define HIBMC_DP_CFG_STREAM_HTOTAL_SIZE GENMASK(31, 16)
#define HIBMC_DP_CFG_STREAM_HBLANK_SIZE GENMASK(15, 0)
#endif

View File

@ -0,0 +1,118 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2024 Hisilicon Limited.
#include <linux/io.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_simple_kms_helper.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_modes.h>
#include <drm/drm_drv.h>
#include <drm/drm_edid.h>
#include "hibmc_drm_drv.h"
#include "dp/dp_hw.h"
static int hibmc_dp_connector_get_modes(struct drm_connector *connector)
{
int count;
count = drm_add_modes_noedid(connector, connector->dev->mode_config.max_width,
connector->dev->mode_config.max_height);
drm_set_preferred_mode(connector, 1024, 768); // temporary implementation
return count;
}
static const struct drm_connector_helper_funcs hibmc_dp_conn_helper_funcs = {
.get_modes = hibmc_dp_connector_get_modes,
};
static const struct drm_connector_funcs hibmc_dp_conn_funcs = {
.reset = drm_atomic_helper_connector_reset,
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = drm_connector_cleanup,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};
static inline int hibmc_dp_prepare(struct hibmc_dp *dp, struct drm_display_mode *mode)
{
int ret;
hibmc_dp_display_en(dp, false);
ret = hibmc_dp_mode_set(dp, mode);
if (ret)
drm_err(dp->drm_dev, "hibmc dp mode set failed: %d\n", ret);
return ret;
}
static void hibmc_dp_encoder_enable(struct drm_encoder *drm_encoder,
struct drm_atomic_state *state)
{
struct hibmc_dp *dp = container_of(drm_encoder, struct hibmc_dp, encoder);
struct drm_display_mode *mode = &drm_encoder->crtc->state->mode;
if (hibmc_dp_prepare(dp, mode))
return;
hibmc_dp_display_en(dp, true);
}
static void hibmc_dp_encoder_disable(struct drm_encoder *drm_encoder,
struct drm_atomic_state *state)
{
struct hibmc_dp *dp = container_of(drm_encoder, struct hibmc_dp, encoder);
hibmc_dp_display_en(dp, false);
}
static const struct drm_encoder_helper_funcs hibmc_dp_encoder_helper_funcs = {
.atomic_enable = hibmc_dp_encoder_enable,
.atomic_disable = hibmc_dp_encoder_disable,
};
int hibmc_dp_init(struct hibmc_drm_private *priv)
{
struct drm_device *dev = &priv->dev;
struct drm_crtc *crtc = &priv->crtc;
struct hibmc_dp *dp = &priv->dp;
struct drm_connector *connector = &dp->connector;
struct drm_encoder *encoder = &dp->encoder;
int ret;
dp->mmio = priv->mmio;
dp->drm_dev = dev;
ret = hibmc_dp_hw_init(&priv->dp);
if (ret) {
drm_err(dev, "hibmc dp hw init failed: %d\n", ret);
return ret;
}
hibmc_dp_display_en(&priv->dp, false);
encoder->possible_crtcs = drm_crtc_mask(crtc);
ret = drmm_encoder_init(dev, encoder, NULL, DRM_MODE_ENCODER_TMDS, NULL);
if (ret) {
drm_err(dev, "init dp encoder failed: %d\n", ret);
return ret;
}
drm_encoder_helper_add(encoder, &hibmc_dp_encoder_helper_funcs);
ret = drm_connector_init(dev, connector, &hibmc_dp_conn_funcs,
DRM_MODE_CONNECTOR_DisplayPort);
if (ret) {
drm_err(dev, "init dp connector failed: %d\n", ret);
return ret;
}
drm_connector_helper_add(connector, &hibmc_dp_conn_helper_funcs);
drm_connector_attach_encoder(connector, encoder);
return 0;
}

View File

@ -28,6 +28,10 @@
#include "hibmc_drm_drv.h"
#include "hibmc_drm_regs.h"
#define HIBMC_DP_HOST_SERDES_CTRL 0x1f001c
#define HIBMC_DP_HOST_SERDES_CTRL_VAL 0x8a00
#define HIBMC_DP_HOST_SERDES_CTRL_MASK 0x7ffff
DEFINE_DRM_GEM_FOPS(hibmc_fops);
static irqreturn_t hibmc_interrupt(int irq, void *arg)
@ -117,6 +121,14 @@ static int hibmc_kms_init(struct hibmc_drm_private *priv)
return ret;
}
/* if DP existed, init DP */
if ((readl(priv->mmio + HIBMC_DP_HOST_SERDES_CTRL) &
HIBMC_DP_HOST_SERDES_CTRL_MASK) == HIBMC_DP_HOST_SERDES_CTRL_VAL) {
ret = hibmc_dp_init(priv);
if (ret)
drm_err(dev, "failed to init dp: %d\n", ret);
}
ret = hibmc_vdac_init(priv);
if (ret) {
drm_err(dev, "failed to init vdac: %d\n", ret);
@ -327,6 +339,8 @@ static int hibmc_pci_probe(struct pci_dev *pdev,
goto err_return;
}
pci_set_master(pdev);
ret = hibmc_load(dev);
if (ret) {
drm_err(dev, "failed to load hibmc: %d\n", ret);

View File

@ -20,9 +20,12 @@
#include <drm/drm_framebuffer.h>
struct hibmc_connector {
struct drm_connector base;
#include "dp/dp_hw.h"
struct hibmc_vdac {
struct drm_device *dev;
struct drm_encoder encoder;
struct drm_connector connector;
struct i2c_adapter adapter;
struct i2c_algo_bit_data bit_data;
};
@ -35,13 +38,13 @@ struct hibmc_drm_private {
struct drm_device dev;
struct drm_plane primary_plane;
struct drm_crtc crtc;
struct drm_encoder encoder;
struct hibmc_connector connector;
struct hibmc_vdac vdac;
struct hibmc_dp dp;
};
static inline struct hibmc_connector *to_hibmc_connector(struct drm_connector *connector)
static inline struct hibmc_vdac *to_hibmc_vdac(struct drm_connector *connector)
{
return container_of(connector, struct hibmc_connector, base);
return container_of(connector, struct hibmc_vdac, connector);
}
static inline struct hibmc_drm_private *to_hibmc_drm_private(struct drm_device *dev)
@ -57,6 +60,8 @@ void hibmc_set_current_gate(struct hibmc_drm_private *priv,
int hibmc_de_init(struct hibmc_drm_private *priv);
int hibmc_vdac_init(struct hibmc_drm_private *priv);
int hibmc_ddc_create(struct drm_device *drm_dev, struct hibmc_connector *connector);
int hibmc_ddc_create(struct drm_device *drm_dev, struct hibmc_vdac *connector);
int hibmc_dp_init(struct hibmc_drm_private *priv);
#endif

View File

@ -25,8 +25,8 @@
static void hibmc_set_i2c_signal(void *data, u32 mask, int value)
{
struct hibmc_connector *hibmc_connector = data;
struct hibmc_drm_private *priv = to_hibmc_drm_private(hibmc_connector->base.dev);
struct hibmc_vdac *vdac = data;
struct hibmc_drm_private *priv = to_hibmc_drm_private(vdac->connector.dev);
u32 tmp_dir = readl(priv->mmio + GPIO_DATA_DIRECTION);
if (value) {
@ -45,8 +45,8 @@ static void hibmc_set_i2c_signal(void *data, u32 mask, int value)
static int hibmc_get_i2c_signal(void *data, u32 mask)
{
struct hibmc_connector *hibmc_connector = data;
struct hibmc_drm_private *priv = to_hibmc_drm_private(hibmc_connector->base.dev);
struct hibmc_vdac *vdac = data;
struct hibmc_drm_private *priv = to_hibmc_drm_private(vdac->connector.dev);
u32 tmp_dir = readl(priv->mmio + GPIO_DATA_DIRECTION);
if ((tmp_dir & mask) != mask) {
@ -77,22 +77,21 @@ static int hibmc_ddc_getscl(void *data)
return hibmc_get_i2c_signal(data, I2C_SCL_MASK);
}
int hibmc_ddc_create(struct drm_device *drm_dev,
struct hibmc_connector *connector)
int hibmc_ddc_create(struct drm_device *drm_dev, struct hibmc_vdac *vdac)
{
connector->adapter.owner = THIS_MODULE;
snprintf(connector->adapter.name, I2C_NAME_SIZE, "HIS i2c bit bus");
connector->adapter.dev.parent = drm_dev->dev;
i2c_set_adapdata(&connector->adapter, connector);
connector->adapter.algo_data = &connector->bit_data;
vdac->adapter.owner = THIS_MODULE;
snprintf(vdac->adapter.name, I2C_NAME_SIZE, "HIS i2c bit bus");
vdac->adapter.dev.parent = drm_dev->dev;
i2c_set_adapdata(&vdac->adapter, vdac);
vdac->adapter.algo_data = &vdac->bit_data;
connector->bit_data.udelay = 20;
connector->bit_data.timeout = usecs_to_jiffies(2000);
connector->bit_data.data = connector;
connector->bit_data.setsda = hibmc_ddc_setsda;
connector->bit_data.setscl = hibmc_ddc_setscl;
connector->bit_data.getsda = hibmc_ddc_getsda;
connector->bit_data.getscl = hibmc_ddc_getscl;
vdac->bit_data.udelay = 20;
vdac->bit_data.timeout = usecs_to_jiffies(2000);
vdac->bit_data.data = vdac;
vdac->bit_data.setsda = hibmc_ddc_setsda;
vdac->bit_data.setscl = hibmc_ddc_setscl;
vdac->bit_data.getsda = hibmc_ddc_getsda;
vdac->bit_data.getscl = hibmc_ddc_getscl;
return i2c_bit_add_bus(&connector->adapter);
return i2c_bit_add_bus(&vdac->adapter);
}

View File

@ -24,11 +24,11 @@
static int hibmc_connector_get_modes(struct drm_connector *connector)
{
struct hibmc_connector *hibmc_connector = to_hibmc_connector(connector);
struct hibmc_vdac *vdac = to_hibmc_vdac(connector);
const struct drm_edid *drm_edid;
int count;
drm_edid = drm_edid_read_ddc(connector, &hibmc_connector->adapter);
drm_edid = drm_edid_read_ddc(connector, &vdac->adapter);
drm_edid_connector_update(connector, drm_edid);
@ -51,9 +51,9 @@ static int hibmc_connector_get_modes(struct drm_connector *connector)
static void hibmc_connector_destroy(struct drm_connector *connector)
{
struct hibmc_connector *hibmc_connector = to_hibmc_connector(connector);
struct hibmc_vdac *vdac = to_hibmc_vdac(connector);
i2c_del_adapter(&hibmc_connector->adapter);
i2c_del_adapter(&vdac->adapter);
drm_connector_cleanup(connector);
}
@ -93,20 +93,20 @@ static const struct drm_encoder_helper_funcs hibmc_encoder_helper_funcs = {
int hibmc_vdac_init(struct hibmc_drm_private *priv)
{
struct drm_device *dev = &priv->dev;
struct hibmc_connector *hibmc_connector = &priv->connector;
struct drm_encoder *encoder = &priv->encoder;
struct hibmc_vdac *vdac = &priv->vdac;
struct drm_encoder *encoder = &vdac->encoder;
struct drm_crtc *crtc = &priv->crtc;
struct drm_connector *connector = &hibmc_connector->base;
struct drm_connector *connector = &vdac->connector;
int ret;
ret = hibmc_ddc_create(dev, hibmc_connector);
ret = hibmc_ddc_create(dev, vdac);
if (ret) {
drm_err(dev, "failed to create ddc: %d\n", ret);
return ret;
}
encoder->possible_crtcs = drm_crtc_mask(crtc);
ret = drm_simple_encoder_init(dev, encoder, DRM_MODE_ENCODER_DAC);
ret = drmm_encoder_init(dev, encoder, NULL, DRM_MODE_ENCODER_DAC, NULL);
if (ret) {
drm_err(dev, "failed to init encoder: %d\n", ret);
return ret;
@ -117,7 +117,7 @@ int hibmc_vdac_init(struct hibmc_drm_private *priv)
ret = drm_connector_init_with_ddc(dev, connector,
&hibmc_connector_funcs,
DRM_MODE_CONNECTOR_VGA,
&hibmc_connector->adapter);
&vdac->adapter);
if (ret) {
drm_err(dev, "failed to init connector: %d\n", ret);
return ret;

View File

@ -1165,7 +1165,6 @@ static const struct hdmi_codec_ops audio_codec_ops = {
.audio_shutdown = tda998x_audio_shutdown,
.mute_stream = tda998x_audio_mute_stream,
.get_eld = tda998x_audio_get_eld,
.no_capture_mute = 1,
};
static int tda998x_audio_codec_init(struct tda998x_priv *priv,
@ -1176,6 +1175,7 @@ static int tda998x_audio_codec_init(struct tda998x_priv *priv,
.max_i2s_channels = 2,
.no_i2s_capture = 1,
.no_spdif_capture = 1,
.no_capture_mute = 1,
};
if (priv->audio_port_enable[AUDIO_ROUTE_I2S])

View File

@ -102,6 +102,7 @@ static void show_meminfo(struct drm_printer *p, struct drm_file *file)
for_each_memory_region(mr, i915, id)
drm_print_memory_stats(p,
&stats[id],
DRM_GEM_OBJECT_ACTIVE |
DRM_GEM_OBJECT_RESIDENT |
DRM_GEM_OBJECT_PURGEABLE,
mr->uabi_name);

View File

@ -2638,7 +2638,6 @@ static const struct hdmi_codec_ops mtk_dp_audio_codec_ops = {
.audio_shutdown = mtk_dp_audio_shutdown,
.get_eld = mtk_dp_audio_get_eld,
.hook_plugged_cb = mtk_dp_audio_hook_plugged_cb,
.no_capture_mute = 1,
};
static int mtk_dp_register_audio_driver(struct device *dev)
@ -2649,6 +2648,7 @@ static int mtk_dp_register_audio_driver(struct device *dev)
.max_i2s_channels = 8,
.i2s = 1,
.data = mtk_dp,
.no_capture_mute = 1,
};
mtk_dp->audio_pdev = platform_device_register_data(dev,

View File

@ -1660,7 +1660,6 @@ static const struct hdmi_codec_ops mtk_hdmi_audio_codec_ops = {
.mute_stream = mtk_hdmi_audio_mute,
.get_eld = mtk_hdmi_audio_get_eld,
.hook_plugged_cb = mtk_hdmi_audio_hook_plugged_cb,
.no_capture_mute = 1,
};
static int mtk_hdmi_register_audio_driver(struct device *dev)
@ -1671,6 +1670,7 @@ static int mtk_hdmi_register_audio_driver(struct device *dev)
.max_i2s_channels = 2,
.i2s = 1,
.data = hdmi,
.no_capture_mute = 1,
};
struct platform_device *pdev;

View File

@ -1831,7 +1831,7 @@ static int dsi_host_parse_dt(struct msm_dsi_host *msm_host)
msm_dsi->te_source = devm_kstrdup(dev, te_source, GFP_KERNEL);
ret = 0;
if (of_property_read_bool(np, "syscon-sfpb")) {
if (of_property_present(np, "syscon-sfpb")) {
msm_host->sfpb = syscon_regmap_lookup_by_phandle(np,
"syscon-sfpb");
if (IS_ERR(msm_host->sfpb)) {

View File

@ -4454,6 +4454,37 @@ static const struct panel_desc ti_nspire_classic_lcd_panel = {
.bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE,
};
static const struct display_timing topland_tian_g07017_01_timing = {
.pixelclock = { 44900000, 51200000, 63000000 },
.hactive = { 1024, 1024, 1024 },
.hfront_porch = { 16, 160, 216 },
.hback_porch = { 160, 160, 160 },
.hsync_len = { 1, 1, 140 },
.vactive = { 600, 600, 600 },
.vfront_porch = { 1, 12, 127 },
.vback_porch = { 23, 23, 23 },
.vsync_len = { 1, 1, 20 },
};
static const struct panel_desc topland_tian_g07017_01 = {
.timings = &topland_tian_g07017_01_timing,
.num_timings = 1,
.bpc = 8,
.size = {
.width = 154,
.height = 86,
},
.delay = {
.prepare = 1, /* 6.5 - 150µs PLL wake-up time */
.enable = 100, /* 6.4 - Power on: 6 VSyncs */
.disable = 84, /* 6.4 - Power off: 5 Vsyncs */
.unprepare = 50, /* 6.4 - Power off: 3 Vsyncs */
},
.bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG,
.connector_type = DRM_MODE_CONNECTOR_LVDS,
.bus_flags = DRM_BUS_FLAG_DE_HIGH,
};
static const struct drm_display_mode toshiba_lt089ac29000_mode = {
.clock = 79500,
.hdisplay = 1280,
@ -5139,6 +5170,9 @@ static const struct of_device_id platform_of_match[] = {
}, {
.compatible = "toshiba,lt089ac29000",
.data = &toshiba_lt089ac29000,
}, {
.compatible = "topland,tian-g07017-01",
.data = &topland_tian_g07017_01,
}, {
.compatible = "tpk,f07a-0102",
.data = &tpk_f07a_0102,

View File

@ -885,7 +885,6 @@ static const struct hdmi_codec_ops audio_codec_ops = {
.mute_stream = cdn_dp_audio_mute_stream,
.get_eld = cdn_dp_audio_get_eld,
.hook_plugged_cb = cdn_dp_audio_hook_plugged_cb,
.no_capture_mute = 1,
};
static int cdn_dp_audio_codec_init(struct cdn_dp_device *dp,
@ -896,6 +895,7 @@ static int cdn_dp_audio_codec_init(struct cdn_dp_device *dp,
.spdif = 1,
.ops = &audio_codec_ops,
.max_i2s_channels = 8,
.no_capture_mute = 1,
};
dp->audio_pdev = platform_device_register_data(

View File

@ -1454,16 +1454,18 @@ static void vop2_plane_atomic_update(struct drm_plane *plane,
vop2_win_write(win, VOP2_WIN_AFBC_HALF_BLOCK_EN, half_block_en);
if (afbc_en) {
u32 stride;
u32 stride, block_w;
/* the afbc superblock is 16 x 16 or 32 x 8 */
block_w = fb->modifier & AFBC_FORMAT_MOD_BLOCK_SIZE_32x8 ? 32 : 16;
/* the afbc superblock is 16 x 16 */
afbc_format = vop2_convert_afbc_format(fb->format->format);
/* Enable color transform for YTR */
if (fb->modifier & AFBC_FORMAT_MOD_YTR)
afbc_format |= (1 << 4);
afbc_tile_num = ALIGN(actual_w, 16) >> 4;
afbc_tile_num = ALIGN(actual_w, block_w) / block_w;
/*
* AFBC pic_vir_width is count by pixel, this is different
@ -1474,6 +1476,9 @@ static void vop2_plane_atomic_update(struct drm_plane *plane,
drm_dbg_kms(vop2->drm, "vp%d %s stride[%d] not 64 pixel aligned\n",
vp->id, win->data->name, stride);
/* It's for head stride, each head size is 16 byte */
stride = ALIGN(stride, block_w) / block_w * 16;
uv_swap = vop2_afbc_uv_swap(fb->format->format);
/*
* This is a workaround for crazy IC design, Cluster
@ -1504,7 +1509,11 @@ static void vop2_plane_atomic_update(struct drm_plane *plane,
else
vop2_win_write(win, VOP2_WIN_AFBC_AUTO_GATING_EN, 1);
vop2_win_write(win, VOP2_WIN_AFBC_BLOCK_SPLIT_EN, 0);
if (fb->modifier & AFBC_FORMAT_MOD_SPLIT)
vop2_win_write(win, VOP2_WIN_AFBC_BLOCK_SPLIT_EN, 1);
else
vop2_win_write(win, VOP2_WIN_AFBC_BLOCK_SPLIT_EN, 0);
transform_offset = vop2_afbc_transform_offset(pstate, half_block_en);
vop2_win_write(win, VOP2_WIN_AFBC_HDR_PTR, yrgb_mst);
vop2_win_write(win, VOP2_WIN_AFBC_PIC_SIZE, act_info);

View File

@ -1355,7 +1355,8 @@ EXPORT_SYMBOL(drm_sched_init);
* drm_sched_backend_ops.run_job(). Consequently, drm_sched_backend_ops.free_job()
* will not be called for all jobs still in drm_gpu_scheduler.pending_list.
* There is no solution for this currently. Thus, it is up to the driver to make
* sure that
* sure that:
*
* a) drm_sched_fini() is only called after for all submitted jobs
* drm_sched_backend_ops.free_job() has been called or that
* b) the jobs for which drm_sched_backend_ops.free_job() has not been called

View File

@ -1237,7 +1237,6 @@ static const struct hdmi_codec_ops audio_codec_ops = {
.audio_shutdown = hdmi_audio_shutdown,
.mute_stream = hdmi_audio_mute,
.get_eld = hdmi_audio_get_eld,
.no_capture_mute = 1,
};
static int sti_hdmi_register_audio_driver(struct device *dev,
@ -1247,6 +1246,7 @@ static int sti_hdmi_register_audio_driver(struct device *dev,
.ops = &audio_codec_ops,
.max_i2s_channels = 8,
.i2s = 1,
.no_capture_mute = 1,
};
DRM_DEBUG_DRIVER("\n");

View File

@ -10,6 +10,7 @@ config DRM_VC4
depends on COMMON_CLK
depends on PM
select DRM_CLIENT_SELECTION
select DRM_DISPLAY_HDMI_AUDIO_HELPER
select DRM_DISPLAY_HDMI_HELPER
select DRM_DISPLAY_HDMI_STATE_HELPER
select DRM_DISPLAY_HELPER

View File

@ -31,6 +31,7 @@
* encoder block has CEC support.
*/
#include <drm/display/drm_hdmi_audio_helper.h>
#include <drm/display/drm_hdmi_helper.h>
#include <drm/display/drm_hdmi_state_helper.h>
#include <drm/display/drm_scdc_helper.h>
@ -383,7 +384,6 @@ static void vc4_hdmi_handle_hotplug(struct vc4_hdmi *vc4_hdmi,
enum drm_connector_status status)
{
struct drm_connector *connector = &vc4_hdmi->connector;
const struct drm_edid *drm_edid;
int ret;
/*
@ -405,17 +405,14 @@ static void vc4_hdmi_handle_hotplug(struct vc4_hdmi *vc4_hdmi,
return;
}
drm_edid = drm_edid_read_ddc(connector, vc4_hdmi->ddc);
drm_atomic_helper_connector_hdmi_hotplug(connector, status);
drm_edid_connector_update(connector, drm_edid);
cec_s_phys_addr(vc4_hdmi->cec_adap,
connector->display_info.source_physical_address, false);
if (!drm_edid)
if (status != connector_status_connected)
return;
drm_edid_free(drm_edid);
for (;;) {
ret = vc4_hdmi_reset_link(connector, ctx);
if (ret == -EDEADLK) {
@ -470,31 +467,10 @@ static int vc4_hdmi_connector_detect_ctx(struct drm_connector *connector,
static int vc4_hdmi_connector_get_modes(struct drm_connector *connector)
{
struct vc4_hdmi *vc4_hdmi = connector_to_vc4_hdmi(connector);
struct vc4_dev *vc4 = to_vc4_dev(connector->dev);
const struct drm_edid *drm_edid;
int ret = 0;
/*
* NOTE: This function should really take vc4_hdmi->mutex, but doing so
* results in reentrancy issues since cec_s_phys_addr() might call
* .adap_enable, which leads to that funtion being called with our mutex
* held.
*
* Concurrency isn't an issue at the moment since we don't share
* any state with any of the other frameworks so we can ignore
* the lock for now.
*/
drm_edid = drm_edid_read_ddc(connector, vc4_hdmi->ddc);
drm_edid_connector_update(connector, drm_edid);
cec_s_phys_addr(vc4_hdmi->cec_adap,
connector->display_info.source_physical_address, false);
if (!drm_edid)
return 0;
ret = drm_edid_connector_add_modes(connector);
drm_edid_free(drm_edid);
if (!vc4->hvs->vc5_hdmi_enable_hdmi_20) {
struct drm_device *drm = connector->dev;
@ -570,6 +546,7 @@ static void vc4_hdmi_connector_reset(struct drm_connector *connector)
}
static const struct drm_connector_funcs vc4_hdmi_connector_funcs = {
.force = drm_atomic_helper_connector_hdmi_force,
.fill_modes = drm_helper_probe_single_connector_modes,
.reset = vc4_hdmi_connector_reset,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
@ -584,6 +561,7 @@ static const struct drm_connector_helper_funcs vc4_hdmi_connector_helper_funcs =
};
static const struct drm_connector_hdmi_funcs vc4_hdmi_hdmi_connector_funcs;
static const struct drm_connector_hdmi_audio_funcs vc4_hdmi_audio_funcs;
static int vc4_hdmi_connector_init(struct drm_device *dev,
struct vc4_hdmi *vc4_hdmi)
@ -609,6 +587,12 @@ static int vc4_hdmi_connector_init(struct drm_device *dev,
if (ret)
return ret;
ret = drm_connector_hdmi_audio_init(connector, dev->dev,
&vc4_hdmi_audio_funcs,
8, false, -1);
if (ret)
return ret;
drm_connector_helper_add(connector, &vc4_hdmi_connector_helper_funcs);
/*
@ -1921,9 +1905,9 @@ static bool vc4_hdmi_audio_can_stream(struct vc4_hdmi *vc4_hdmi)
return true;
}
static int vc4_hdmi_audio_startup(struct device *dev, void *data)
static int vc4_hdmi_audio_startup(struct drm_connector *connector)
{
struct vc4_hdmi *vc4_hdmi = dev_get_drvdata(dev);
struct vc4_hdmi *vc4_hdmi = connector_to_vc4_hdmi(connector);
struct drm_device *drm = vc4_hdmi->connector.dev;
unsigned long flags;
int ret = 0;
@ -1985,9 +1969,9 @@ static void vc4_hdmi_audio_reset(struct vc4_hdmi *vc4_hdmi)
spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
}
static void vc4_hdmi_audio_shutdown(struct device *dev, void *data)
static void vc4_hdmi_audio_shutdown(struct drm_connector *connector)
{
struct vc4_hdmi *vc4_hdmi = dev_get_drvdata(dev);
struct vc4_hdmi *vc4_hdmi = connector_to_vc4_hdmi(connector);
struct drm_device *drm = vc4_hdmi->connector.dev;
unsigned long flags;
int idx;
@ -2057,13 +2041,12 @@ static int sample_rate_to_mai_fmt(int samplerate)
}
/* HDMI audio codec callbacks */
static int vc4_hdmi_audio_prepare(struct device *dev, void *data,
static int vc4_hdmi_audio_prepare(struct drm_connector *connector,
struct hdmi_codec_daifmt *daifmt,
struct hdmi_codec_params *params)
{
struct vc4_hdmi *vc4_hdmi = dev_get_drvdata(dev);
struct vc4_hdmi *vc4_hdmi = connector_to_vc4_hdmi(connector);
struct drm_device *drm = vc4_hdmi->connector.dev;
struct drm_connector *connector = &vc4_hdmi->connector;
struct vc4_dev *vc4 = to_vc4_dev(drm);
unsigned int sample_rate = params->sample_rate;
unsigned int channels = params->channels;
@ -2075,7 +2058,7 @@ static int vc4_hdmi_audio_prepare(struct device *dev, void *data,
int ret = 0;
int idx;
dev_dbg(dev, "%s: %u Hz, %d bit, %d channels\n", __func__,
dev_dbg(&vc4_hdmi->pdev->dev, "%s: %u Hz, %d bit, %d channels\n", __func__,
sample_rate, params->sample_width, channels);
mutex_lock(&vc4_hdmi->mutex);
@ -2214,40 +2197,12 @@ static const struct snd_dmaengine_pcm_config pcm_conf = {
.prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config,
};
static int vc4_hdmi_audio_get_eld(struct device *dev, void *data,
uint8_t *buf, size_t len)
{
struct vc4_hdmi *vc4_hdmi = dev_get_drvdata(dev);
struct drm_connector *connector = &vc4_hdmi->connector;
mutex_lock(&connector->eld_mutex);
memcpy(buf, connector->eld, min(sizeof(connector->eld), len));
mutex_unlock(&connector->eld_mutex);
return 0;
}
static const struct hdmi_codec_ops vc4_hdmi_codec_ops = {
.get_eld = vc4_hdmi_audio_get_eld,
static const struct drm_connector_hdmi_audio_funcs vc4_hdmi_audio_funcs = {
.startup = vc4_hdmi_audio_startup,
.prepare = vc4_hdmi_audio_prepare,
.audio_shutdown = vc4_hdmi_audio_shutdown,
.audio_startup = vc4_hdmi_audio_startup,
.shutdown = vc4_hdmi_audio_shutdown,
};
static struct hdmi_codec_pdata vc4_hdmi_codec_pdata = {
.ops = &vc4_hdmi_codec_ops,
.max_i2s_channels = 8,
.i2s = 1,
};
static void vc4_hdmi_audio_codec_release(void *ptr)
{
struct vc4_hdmi *vc4_hdmi = ptr;
platform_device_unregister(vc4_hdmi->audio.codec_pdev);
vc4_hdmi->audio.codec_pdev = NULL;
}
static int vc4_hdmi_audio_init(struct vc4_hdmi *vc4_hdmi)
{
const struct vc4_hdmi_register *mai_data =
@ -2255,7 +2210,6 @@ static int vc4_hdmi_audio_init(struct vc4_hdmi *vc4_hdmi)
struct snd_soc_dai_link *dai_link = &vc4_hdmi->audio.link;
struct snd_soc_card *card = &vc4_hdmi->audio.card;
struct device *dev = &vc4_hdmi->pdev->dev;
struct platform_device *codec_pdev;
const __be32 *addr;
int index, len;
int ret;
@ -2348,20 +2302,6 @@ static int vc4_hdmi_audio_init(struct vc4_hdmi *vc4_hdmi)
return ret;
}
codec_pdev = platform_device_register_data(dev, HDMI_CODEC_DRV_NAME,
PLATFORM_DEVID_AUTO,
&vc4_hdmi_codec_pdata,
sizeof(vc4_hdmi_codec_pdata));
if (IS_ERR(codec_pdev)) {
dev_err(dev, "Couldn't register the HDMI codec: %ld\n", PTR_ERR(codec_pdev));
return PTR_ERR(codec_pdev);
}
vc4_hdmi->audio.codec_pdev = codec_pdev;
ret = devm_add_action_or_reset(dev, vc4_hdmi_audio_codec_release, vc4_hdmi);
if (ret)
return ret;
dai_link->cpus = &vc4_hdmi->audio.cpu;
dai_link->codecs = &vc4_hdmi->audio.codec;
dai_link->platforms = &vc4_hdmi->audio.platform;
@ -2374,7 +2314,7 @@ static int vc4_hdmi_audio_init(struct vc4_hdmi *vc4_hdmi)
dai_link->stream_name = "MAI PCM";
dai_link->codecs->dai_name = "i2s-hifi";
dai_link->cpus->dai_name = dev_name(dev);
dai_link->codecs->name = dev_name(&codec_pdev->dev);
dai_link->codecs->name = dev_name(&vc4_hdmi->connector.hdmi_audio.codec_pdev->dev);
dai_link->platforms->name = dev_name(dev);
card->dai_link = dai_link;

View File

@ -104,8 +104,6 @@ struct vc4_hdmi_audio {
struct snd_soc_dai_link_component codec;
struct snd_soc_dai_link_component platform;
struct snd_dmaengine_dai_dma_data dma_data;
struct hdmi_audio_infoframe infoframe;
struct platform_device *codec_pdev;
bool streaming;
};

View File

@ -261,6 +261,7 @@ static void show_meminfo(struct drm_printer *p, struct drm_file *file)
if (man) {
drm_print_memory_stats(p,
&stats[mem_type],
DRM_GEM_OBJECT_ACTIVE |
DRM_GEM_OBJECT_RESIDENT |
(mem_type != XE_PL_SYSTEM ? 0 :
DRM_GEM_OBJECT_PURGEABLE),

View File

@ -0,0 +1,22 @@
/* SPDX-License-Identifier: MIT */
#ifndef DRM_DISPLAY_HDMI_AUDIO_HELPER_H_
#define DRM_DISPLAY_HDMI_AUDIO_HELPER_H_
#include <linux/types.h>
struct drm_connector;
struct drm_connector_hdmi_audio_funcs;
struct device;
int drm_connector_hdmi_audio_init(struct drm_connector *connector,
struct device *hdmi_codec_dev,
const struct drm_connector_hdmi_audio_funcs *funcs,
unsigned int max_i2s_playback_channels,
bool spdif_playback,
int sound_dai_port);
void drm_connector_hdmi_audio_plugged_notify(struct drm_connector *connector,
bool plugged);
#endif

View File

@ -8,6 +8,8 @@ struct drm_connector;
struct drm_connector_state;
struct hdmi_audio_infoframe;
enum drm_connector_status;
void __drm_atomic_helper_connector_hdmi_reset(struct drm_connector *connector,
struct drm_connector_state *new_conn_state);
@ -19,6 +21,9 @@ int drm_atomic_helper_connector_hdmi_update_audio_infoframe(struct drm_connector
int drm_atomic_helper_connector_hdmi_clear_audio_infoframe(struct drm_connector *connector);
int drm_atomic_helper_connector_hdmi_update_infoframes(struct drm_connector *connector,
struct drm_atomic_state *state);
void drm_atomic_helper_connector_hdmi_hotplug(struct drm_connector *connector,
enum drm_connector_status status);
void drm_atomic_helper_connector_hdmi_force(struct drm_connector *connector);
enum drm_mode_status
drm_hdmi_connector_mode_valid(struct drm_connector *connector,

View File

@ -41,6 +41,8 @@ struct drm_display_info;
struct drm_minor;
struct drm_panel;
struct edid;
struct hdmi_codec_daifmt;
struct hdmi_codec_params;
struct i2c_adapter;
/**
@ -676,6 +678,57 @@ struct drm_bridge_funcs {
enum hdmi_infoframe_type type,
const u8 *buffer, size_t len);
/**
* @hdmi_audio_startup:
*
* Called when ASoC starts an audio stream setup. The
* @hdmi_audio_startup() is optional.
*
* Returns:
* 0 on success, a negative error code otherwise
*/
int (*hdmi_audio_startup)(struct drm_connector *connector,
struct drm_bridge *bridge);
/**
* @prepare:
* Configures HDMI-encoder for audio stream. Can be called multiple
* times for each setup. Mandatory if HDMI audio is enabled in the
* bridge's configuration.
*
* Returns:
* 0 on success, a negative error code otherwise
*/
int (*hdmi_audio_prepare)(struct drm_connector *connector,
struct drm_bridge *bridge,
struct hdmi_codec_daifmt *fmt,
struct hdmi_codec_params *hparms);
/**
* @hdmi_audio_shutdown:
*
* Shut down the audio stream. Mandatory if HDMI audio is enabled in
* the bridge's configuration.
*
* Returns:
* 0 on success, a negative error code otherwise
*/
void (*hdmi_audio_shutdown)(struct drm_connector *connector,
struct drm_bridge *bridge);
/**
* @hdmi_audio_mute_stream:
*
* Mute/unmute HDMI audio stream. The @hdmi_audio_mute_stream callback
* is optional.
*
* Returns:
* 0 on success, a negative error code otherwise
*/
int (*hdmi_audio_mute_stream)(struct drm_connector *connector,
struct drm_bridge *bridge,
bool enable, int direction);
/**
* @debugfs_init:
*
@ -859,6 +912,27 @@ struct drm_bridge {
* @DRM_BRIDGE_OP_HDMI is set.
*/
unsigned int max_bpc;
/**
* @hdmi_audio_dev: device to be used as a parent for the HDMI Codec
*/
struct device *hdmi_audio_dev;
/**
* @hdmi_audio_max_i2s_playback_channels: maximum number of playback
* I2S channels for the HDMI codec
*/
int hdmi_audio_max_i2s_playback_channels;
/**
* @hdmi_audio_spdif_playback: set if HDMI codec has S/PDIF playback port
*/
unsigned int hdmi_audio_spdif_playback : 1;
/**
* @hdmi_audio_dai_port: sound DAI port, -1 if it is not enabled
*/
int hdmi_audio_dai_port;
};
static inline struct drm_bridge *

View File

@ -45,7 +45,10 @@ struct drm_property;
struct drm_property_blob;
struct drm_printer;
struct drm_privacy_screen;
struct drm_edid;
struct edid;
struct hdmi_codec_daifmt;
struct hdmi_codec_params;
struct i2c_adapter;
enum drm_connector_force {
@ -1141,6 +1144,53 @@ struct drm_connector_state {
struct drm_connector_hdmi_state hdmi;
};
struct drm_connector_hdmi_audio_funcs {
/**
* @startup:
*
* Called when ASoC starts an audio stream setup. The
* @startup() is optional.
*
* Returns:
* 0 on success, a negative error code otherwise
*/
int (*startup)(struct drm_connector *connector);
/**
* @prepare:
* Configures HDMI-encoder for audio stream. Can be called
* multiple times for each setup. Mandatory.
*
* Returns:
* 0 on success, a negative error code otherwise
*/
int (*prepare)(struct drm_connector *connector,
struct hdmi_codec_daifmt *fmt,
struct hdmi_codec_params *hparms);
/**
* @shutdown:
*
* Shut down the audio stream. Mandatory.
*
* Returns:
* 0 on success, a negative error code otherwise
*/
void (*shutdown)(struct drm_connector *connector);
/**
* @mute_stream:
*
* Mute/unmute HDMI audio stream. The @mute_stream callback is
* optional.
*
* Returns:
* 0 on success, a negative error code otherwise
*/
int (*mute_stream)(struct drm_connector *connector,
bool enable, int direction);
};
/**
* struct drm_connector_hdmi_funcs - drm_hdmi_connector control functions
*/
@ -1198,6 +1248,21 @@ struct drm_connector_hdmi_funcs {
int (*write_infoframe)(struct drm_connector *connector,
enum hdmi_infoframe_type type,
const u8 *buffer, size_t len);
/**
* @read_edid:
*
* This callback is used by the framework as a replacement for reading
* the EDID from connector->ddc. It is still recommended to provide
* connector->ddc instead of implementing this callback. Returned EDID
* should be freed via the drm_edid_free().
*
* The @read_edid callback is optional.
*
* Returns:
* Valid EDID on success, NULL in case of failure.
*/
const struct drm_edid *(*read_edid)(struct drm_connector *connector);
};
/**
@ -1660,6 +1725,68 @@ struct drm_cmdline_mode {
bool tv_mode_specified;
};
/**
* struct drm_connector_hdmi_audio - DRM gemeric HDMI Codec-related structure
*
* HDMI drivers usually incorporate a HDMI Codec. This structure expresses the
* generic HDMI Codec as used by the DRM HDMI Codec framework.
*/
struct drm_connector_hdmi_audio {
/**
* @funcs:
*
* Implementation of the HDMI codec functionality to be used by the DRM
* HDMI Codec framework.
*/
const struct drm_connector_hdmi_audio_funcs *funcs;
/**
* @codec_pdev:
*
* Platform device created to hold the HDMI Codec. It will be
* automatically unregistered during drm_connector_cleanup().
*/
struct platform_device *codec_pdev;
/**
* @lock:
*
* Mutex to protect @last_state, @plugged_cb and @plugged_cb_dev.
*/
struct mutex lock;
/**
* @plugged_cb:
*
* Callback to be called when the HDMI sink get plugged to or unplugged
* from this connector. This is assigned by the framework when
* requested by the ASoC code.
*/
void (*plugged_cb)(struct device *dev, bool plugged);
/**
* @plugged_cb_dev:
*
* The data for @plugged_cb(). It is being provided by the ASoC.
*/
struct device *plugged_cb_dev;
/**
* @last_state:
*
* Last plugged state recored by the framework. It is used to correctly
* report the state to @plugged_cb().
*/
bool last_state;
/**
* @dai_port:
*
* The port in DT that is used for the Codec DAI.
*/
int dai_port;
};
/*
* struct drm_connector_hdmi - DRM Connector HDMI-related structure
*/
@ -2121,6 +2248,11 @@ struct drm_connector {
* @hdmi: HDMI-related variable and properties.
*/
struct drm_connector_hdmi hdmi;
/**
* @hdmi_audio: HDMI codec properties and non-DRM state.
*/
struct drm_connector_hdmi_audio hdmi_audio;
};
#define obj_to_connector(x) container_of(x, struct drm_connector, base)

View File

@ -494,6 +494,7 @@ struct drm_memory_stats {
enum drm_gem_object_status;
int drm_memory_stats_is_zero(const struct drm_memory_stats *stats);
void drm_print_memory_stats(struct drm_printer *p,
const struct drm_memory_stats *stats,
enum drm_gem_object_status supported_status,

View File

@ -48,19 +48,21 @@ struct drm_gem_object;
* enum drm_gem_object_status - bitmask of object state for fdinfo reporting
* @DRM_GEM_OBJECT_RESIDENT: object is resident in memory (ie. not unpinned)
* @DRM_GEM_OBJECT_PURGEABLE: object marked as purgeable by userspace
* @DRM_GEM_OBJECT_ACTIVE: object is currently used by an active submission
*
* Bitmask of status used for fdinfo memory stats, see &drm_gem_object_funcs.status
* and drm_show_fdinfo(). Note that an object can DRM_GEM_OBJECT_PURGEABLE if
* it still active or not resident, in which case drm_show_fdinfo() will not
* and drm_show_fdinfo(). Note that an object can report DRM_GEM_OBJECT_PURGEABLE
* and be active or not resident, in which case drm_show_fdinfo() will not
* account for it as purgeable. So drivers do not need to check if the buffer
* is idle and resident to return this bit. (Ie. userspace can mark a buffer
* as purgeable even while it is still busy on the GPU.. it does not _actually_
* become puregeable until it becomes idle. The status gem object func does
* not need to consider this.)
* is idle and resident to return this bit, i.e. userspace can mark a buffer as
* purgeable even while it is still busy on the GPU. It will not get reported in
* the puregeable stats until it becomes idle. The status gem object func does
* not need to consider this.
*/
enum drm_gem_object_status {
DRM_GEM_OBJECT_RESIDENT = BIT(0),
DRM_GEM_OBJECT_PURGEABLE = BIT(1),
DRM_GEM_OBJECT_ACTIVE = BIT(2),
};
/**

View File

@ -105,7 +105,8 @@ struct hdmi_codec_ops {
* Optional
*/
int (*get_dai_id)(struct snd_soc_component *comment,
struct device_node *endpoint);
struct device_node *endpoint,
void *data);
/*
* Hook callback function to handle connector plug event.
@ -114,9 +115,6 @@ struct hdmi_codec_ops {
int (*hook_plugged_cb)(struct device *dev, void *data,
hdmi_codec_plugged_cb fn,
struct device *codec_dev);
/* bit field */
unsigned int no_capture_mute:1;
};
/* HDMI codec initalization data */
@ -128,6 +126,7 @@ struct hdmi_codec_pdata {
uint spdif:1;
uint no_spdif_playback:1;
uint no_spdif_capture:1;
uint no_capture_mute:1;
int max_i2s_channels;
void *data;
};

View File

@ -714,7 +714,7 @@ static int hdmi_codec_mute(struct snd_soc_dai *dai, int mute, int direction)
*/
if (hcp->hcd.ops->mute_stream &&
(direction == SNDRV_PCM_STREAM_PLAYBACK ||
!hcp->hcd.ops->no_capture_mute))
!hcp->hcd.no_capture_mute))
return hcp->hcd.ops->mute_stream(dai->dev->parent,
hcp->hcd.data,
mute, direction);
@ -995,7 +995,7 @@ static int hdmi_of_xlate_dai_id(struct snd_soc_component *component,
int ret = -ENOTSUPP; /* see snd_soc_get_dai_id() */
if (hcp->hcd.ops->get_dai_id)
ret = hcp->hcd.ops->get_dai_id(component, endpoint);
ret = hcp->hcd.ops->get_dai_id(component, endpoint, hcp->hcd.data);
return ret;
}