mirror of
https://github.com/torvalds/linux.git
synced 2026-04-19 23:34:00 -04:00
Pull drm updates from Dave Airlie:
"There was a rather late merge of a new color pipeline feature, that
some userspace projects are blocked on, and has seen a lot of work in
amdgpu. This should have seen some time in -next. There is additional
support for this for Intel, that if it arrives in the next day or two
I'll pass it on in another pull request and you can decide if you want
to take it.
Highlights:
- Arm Ethos NPU accelerator driver
- new DRM color pipeline support
- amdgpu will now run discrete SI/CIK cards instead of radeon, which
enables vulkan support in userspace
- msm gets gen8 gpu support
- initial Xe3P support in xe
Full detail summary:
New driver:
- Arm Ethos-U65/U85 accel driver
Core:
- support the drm color pipeline in vkms/amdgfx
- add support for drm colorop pipeline
- add COLOR PIPELINE plane property
- add DRM_CLIENT_CAP_PLANE_COLOR_PIPELINE
- throttle dirty worker with vblank
- use drm_for_each_bridge_in_chain_scoped in drm's bridge code
- Ensure drm_client_modeset tests are enabled in UML
- add simulated vblank interrupt - use in drivers
- dumb buffer sizing helper
- move freeing of drm client memory to driver
- crtc sharpness strength property
- stop using system_wq in scheduler/drivers
- support emergency restore in drm-client
Rust:
- make slice::as_flattened usable on all supported rustc
- add FromBytes::from_bytes_prefix() method
- remove redundant device ptr from Rust GEM object
- Change how AlwaysRefCounted is implemented for GEM objects
gpuvm:
- Add deferred vm_bo cleanup to GPUVM (for rust)
atomic:
- cleanup and improve state handling interfaces
buddy:
- optimize block management
dma-buf:
- heaps: Create heap per CMA reserved location
- improve userspace documentation
dp:
- add POST_LT_ADJ_REQ training sequence
- DPCD dSC quirk for synaptics panamera devices
- helpers to query branch DSC max throughput
ttm:
- Rename ttm_bo_put to ttm_bo_fini
- allow page protection flags on risc-v
- rework pipelined eviction fence handling
amdgpu:
- enable amdgpu by default for SI/CI dGPUs
- enable DC by default on SI
- refactor CIK/SI enablement
- add ABM KMS property
- Re-enable DM idle optimizations
- DC Analog encoders support
- Powerplay fixes for fiji/iceland
- Enable DC on bonaire by default
- HMM cleanup
- Add new RAS framework
- DML2.1 updates
- YCbCr420 fixes
- DC FP fixes
- DMUB fixes
- LTTPR fixes
- DTBCLK fixes
- DMU cursor offload handling
- Userq validation improvements
- Unify shutdown callback handling
- Suspend improvements
- Power limit code cleanup
- SR-IOV fixes
- AUX backlight fixes
- DCN 3.5 fixes
- HDMI compliance fixes
- DCN 4.0.1 cursor updates
- DCN interrupt fix
- DC KMS full update improvements
- Add additional HDCP traces
- DCN 3.2 fixes
- DP MST fixes
- Add support for new SR-IOV mailbox interface
- UQ reset support
- HDP flush rework
- VCE1 support
amdkfd:
- HMM cleanups
- Relax checks on save area overallocations
- Fix GPU mappings after prefetch
radeon:
- refactor CIK/SI enablement
xe:
- Initial Xe3P support
- panic support on VRAM for display
- fix stolen size check
- Loosen used tracking restriction
- New SR-IOV debugfs structure and debugfs updates
- Hide the GPU madvise flag behind a VM_BIND flag
- Always expose VRAM provisioning data on discrete GPUs
- Allow VRAM mappings for userptr when used with SVM
- Allow pinning of p2p dma-buf
- Use per-tile debugfs where appropriate
- Add documentation for Execution Queues
- PF improvements
- VF migration recovery redesign work
- User / Kernel VRAM partitioning
- Update Tile-based messages
- Allow configfs to disable specific GT types
- VF provisioning and migration improvements
- use SVM range helpers in PT layer
- Initial CRI support
- access VF registers using dedicated MMIO view
- limit number of jobs per exec queue
- add sriov_admin sysfs tree
- more crescent island specific support
- debugfs residency counter
- SRIOV migration work
- runtime registers for GFX 35
i915:
- add initial Xe3p_LPD display version 35 support
- Enable LNL+ content adaptive sharpness filter
- Use optimized VRR guardband
- Enable Xe3p LT PHY
- enable FBC support for Xe3p_LPD display
- add display 30.02 firmware support
- refactor SKL+ watermark latency setup
- refactor fbdev handling
- call i915/xe runtime PM via function pointers
- refactor i915/xe stolen memory/display interfaces
- use display version instead of gfx version in display code
- extend i915_display_info with Type-C port details
- lots of display cleanups/refactorings
- set O_LARGEFILE in __create_shmem
- skuip guc communication warning on reset
- fix time conversions
- defeature DRRS on LNL+
- refactor intel_frontbuffer split between i915/xe/display
- convert inteL_rom interfaces to struct drm_device
- unify display register polling interfaces
- aovid lock inversion when pinning to GGTT on CHV/BXT+VTD
panel:
- Add KD116N3730A08/A12, chromebook mt8189
- JT101TM023, LQ079L1SX01,
- GLD070WX3-SL01 MIPI DSI
- Samsung LTL106AL0, Samsung LTL106AL01
- Raystar RFF500F-AWH-DNN
- Winstar WF70A8SYJHLNGA
- Wanchanglong w552946aaa
- Samsung SOFEF00
- Lenovo X13s panel
- ilitek-ili9881c - add rpi 5" support
- visionx-rm69299 - add backlight support
- edp - support AUI B116XAN02.0
bridge:
- improve ref counting
- ti-sn65dsi86 - add support for DP mode with HPD
- synopsis: support CEC, init timer with correct freq
- ASL CS5263 DP-to-HDMI bridge support
nova-core:
- introduce bitfield! macro
- introduce safe integer converters
- GSP inits to fully booted state on Ampere
- Use more future-proof register for GPU identification
nova-drm:
- select NOVA_CORE
- 64-bit only
nouveau:
- improve reclocking on tegra 186+
- add large page and compression support
msm:
- GPU:
- Gen8 support: A840 (Kaanapali) and X2-85 (Glymur)
- A612 support
- MDSS:
- Added support for Glymur and QCS8300 platforms
- DPU:
- Enabled Quad-Pipe support, unlocking higher resolutions support
- Added support for Glymur platform
- Documented DPU on QCS8300 platform as supported
- DisplayPort:
- Added support for Glymur platform
- Added support lame remapping inside DP block
- Documented DisplayPort controller on QCS8300 and SM6150/QCS615
as supported
tegra:
- NVJPG driver
panfrost:
- display JM contexts over debugfs
- export JM contexts to userspace
- improve error and job handling
panthor:
- support custom ASN_HASH for mt8196
- support mali-G1 GPU
- flush shmem write before mapping buffers uncached
- make timeout per-queue instead of per-job
mediatek:
- MT8195/88 HDMIv2/DDCv2 support
rockchip:
- dsi: add support for RK3368
amdxdna:
- enhance runtime PM
- last hardware error reading uapi
- support firmware debug output
- add resource and telemetry data uapi
- preemption support
imx:
- add driver for HDMI TX Parallel audio interface
ivpu:
- add support for user-managed preemption buffer
- add userptr support
- update JSM firware API to 3.33.0
- add better alloc/free warnings
- fix page fault in unbind all bos
- rework bind/unbind of imported buffers
- enable MCA ECC signalling
- split fw runtime and global memory buffers
- add fdinfo memory statistics
tidss:
- convert to drm logging
- logging cleanup
ast:
- refactor generation init paths
- add per chip generation detect_tx_chip
- set quirks for each chip model
atmel-hlcdc:
- set LCDC_ATTRE register in plane disable
- set correct values for plane scaler
solomon:
- use drm helper for get_modes and move_valid
sitronix:
- fix output position when clearing screens
qaic:
- support dma-buf exports
- support new firmware's READ_DATA implementation
- sahara AIC200 image table update
- add sysfs support
- add coredump support
- add uevents support
- PM support
sun4i:
- layer refactors to decouple plane from output
- improve DE33 support
vc4:
- switch to generic CEC helpers
komeda:
- use drm_ logging functions
vkms:
- configfs support for display configuration
vgem:
- fix fence timer deadlock
etnaviv:
- add HWDB entry for GC8000 Nano Ultra VIP r6205"
* tag 'drm-next-2025-12-03' of https://gitlab.freedesktop.org/drm/kernel: (1869 commits)
Revert "drm/amd: Skip power ungate during suspend for VPE"
drm/amdgpu: use common defines for HUB faults
drm/amdgpu/gmc12: add amdgpu_vm_handle_fault() handling
drm/amdgpu/gmc11: add amdgpu_vm_handle_fault() handling
drm/amdgpu: use static ids for ACP platform devs
drm/amdgpu/sdma6: Update SDMA 6.0.3 FW version to include UMQ protected-fence fix
drm/amdgpu: Forward VMID reservation errors
drm/amdgpu/gmc8: Delegate VM faults to soft IRQ handler ring
drm/amdgpu/gmc7: Delegate VM faults to soft IRQ handler ring
drm/amdgpu/gmc6: Delegate VM faults to soft IRQ handler ring
drm/amdgpu/gmc6: Cache VM fault info
drm/amdgpu/gmc6: Don't print MC client as it's unknown
drm/amdgpu/cz_ih: Enable soft IRQ handler ring
drm/amdgpu/tonga_ih: Enable soft IRQ handler ring
drm/amdgpu/iceland_ih: Enable soft IRQ handler ring
drm/amdgpu/cik_ih: Enable soft IRQ handler ring
drm/amdgpu/si_ih: Enable soft IRQ handler ring
drm/amd/display: fix typo in display_mode_core_structs.h
drm/amd/display: fix Smart Power OLED not working after S4
drm/amd/display: Move RGB-type check for audio sync to DCE HW sequence
...
1709 lines
42 KiB
C
1709 lines
42 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (C) 2013 NVIDIA Corporation
|
|
*/
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/host1x.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <linux/regulator/consumer.h>
|
|
#include <linux/reset.h>
|
|
|
|
#include <video/mipi_display.h>
|
|
|
|
#include <drm/drm_atomic_helper.h>
|
|
#include <drm/drm_debugfs.h>
|
|
#include <drm/drm_file.h>
|
|
#include <drm/drm_mipi_dsi.h>
|
|
#include <drm/drm_panel.h>
|
|
#include <drm/drm_print.h>
|
|
#include <drm/drm_simple_kms_helper.h>
|
|
|
|
#include "dc.h"
|
|
#include "drm.h"
|
|
#include "dsi.h"
|
|
#include "mipi-phy.h"
|
|
#include "trace.h"
|
|
|
|
struct tegra_dsi_state {
|
|
struct drm_connector_state base;
|
|
|
|
struct mipi_dphy_timing timing;
|
|
unsigned long period;
|
|
|
|
unsigned int vrefresh;
|
|
unsigned int lanes;
|
|
unsigned long pclk;
|
|
unsigned long bclk;
|
|
|
|
enum tegra_dsi_format format;
|
|
unsigned int mul;
|
|
unsigned int div;
|
|
};
|
|
|
|
static inline struct tegra_dsi_state *
|
|
to_dsi_state(struct drm_connector_state *state)
|
|
{
|
|
return container_of(state, struct tegra_dsi_state, base);
|
|
}
|
|
|
|
struct tegra_dsi {
|
|
struct host1x_client client;
|
|
struct tegra_output output;
|
|
struct device *dev;
|
|
|
|
void __iomem *regs;
|
|
|
|
struct reset_control *rst;
|
|
struct clk *clk_parent;
|
|
struct clk *clk_lp;
|
|
struct clk *clk;
|
|
|
|
struct drm_info_list *debugfs_files;
|
|
|
|
unsigned long flags;
|
|
enum mipi_dsi_pixel_format format;
|
|
unsigned int lanes;
|
|
|
|
struct tegra_mipi_device *mipi;
|
|
struct mipi_dsi_host host;
|
|
|
|
struct regulator *vdd;
|
|
|
|
unsigned int video_fifo_depth;
|
|
unsigned int host_fifo_depth;
|
|
|
|
/* for ganged-mode support */
|
|
struct tegra_dsi *master;
|
|
struct tegra_dsi *slave;
|
|
};
|
|
|
|
static inline struct tegra_dsi *
|
|
host1x_client_to_dsi(struct host1x_client *client)
|
|
{
|
|
return container_of(client, struct tegra_dsi, client);
|
|
}
|
|
|
|
static inline struct tegra_dsi *host_to_tegra(struct mipi_dsi_host *host)
|
|
{
|
|
return container_of(host, struct tegra_dsi, host);
|
|
}
|
|
|
|
static inline struct tegra_dsi *to_dsi(struct tegra_output *output)
|
|
{
|
|
return container_of(output, struct tegra_dsi, output);
|
|
}
|
|
|
|
static struct tegra_dsi_state *tegra_dsi_get_state(struct tegra_dsi *dsi)
|
|
{
|
|
return to_dsi_state(dsi->output.connector.state);
|
|
}
|
|
|
|
static inline u32 tegra_dsi_readl(struct tegra_dsi *dsi, unsigned int offset)
|
|
{
|
|
u32 value = readl(dsi->regs + (offset << 2));
|
|
|
|
trace_dsi_readl(dsi->dev, offset, value);
|
|
|
|
return value;
|
|
}
|
|
|
|
static inline void tegra_dsi_writel(struct tegra_dsi *dsi, u32 value,
|
|
unsigned int offset)
|
|
{
|
|
trace_dsi_writel(dsi->dev, offset, value);
|
|
writel(value, dsi->regs + (offset << 2));
|
|
}
|
|
|
|
#define DEBUGFS_REG32(_name) { .name = #_name, .offset = _name }
|
|
|
|
static const struct debugfs_reg32 tegra_dsi_regs[] = {
|
|
DEBUGFS_REG32(DSI_INCR_SYNCPT),
|
|
DEBUGFS_REG32(DSI_INCR_SYNCPT_CONTROL),
|
|
DEBUGFS_REG32(DSI_INCR_SYNCPT_ERROR),
|
|
DEBUGFS_REG32(DSI_CTXSW),
|
|
DEBUGFS_REG32(DSI_RD_DATA),
|
|
DEBUGFS_REG32(DSI_WR_DATA),
|
|
DEBUGFS_REG32(DSI_POWER_CONTROL),
|
|
DEBUGFS_REG32(DSI_INT_ENABLE),
|
|
DEBUGFS_REG32(DSI_INT_STATUS),
|
|
DEBUGFS_REG32(DSI_INT_MASK),
|
|
DEBUGFS_REG32(DSI_HOST_CONTROL),
|
|
DEBUGFS_REG32(DSI_CONTROL),
|
|
DEBUGFS_REG32(DSI_SOL_DELAY),
|
|
DEBUGFS_REG32(DSI_MAX_THRESHOLD),
|
|
DEBUGFS_REG32(DSI_TRIGGER),
|
|
DEBUGFS_REG32(DSI_TX_CRC),
|
|
DEBUGFS_REG32(DSI_STATUS),
|
|
DEBUGFS_REG32(DSI_INIT_SEQ_CONTROL),
|
|
DEBUGFS_REG32(DSI_INIT_SEQ_DATA_0),
|
|
DEBUGFS_REG32(DSI_INIT_SEQ_DATA_1),
|
|
DEBUGFS_REG32(DSI_INIT_SEQ_DATA_2),
|
|
DEBUGFS_REG32(DSI_INIT_SEQ_DATA_3),
|
|
DEBUGFS_REG32(DSI_INIT_SEQ_DATA_4),
|
|
DEBUGFS_REG32(DSI_INIT_SEQ_DATA_5),
|
|
DEBUGFS_REG32(DSI_INIT_SEQ_DATA_6),
|
|
DEBUGFS_REG32(DSI_INIT_SEQ_DATA_7),
|
|
DEBUGFS_REG32(DSI_PKT_SEQ_0_LO),
|
|
DEBUGFS_REG32(DSI_PKT_SEQ_0_HI),
|
|
DEBUGFS_REG32(DSI_PKT_SEQ_1_LO),
|
|
DEBUGFS_REG32(DSI_PKT_SEQ_1_HI),
|
|
DEBUGFS_REG32(DSI_PKT_SEQ_2_LO),
|
|
DEBUGFS_REG32(DSI_PKT_SEQ_2_HI),
|
|
DEBUGFS_REG32(DSI_PKT_SEQ_3_LO),
|
|
DEBUGFS_REG32(DSI_PKT_SEQ_3_HI),
|
|
DEBUGFS_REG32(DSI_PKT_SEQ_4_LO),
|
|
DEBUGFS_REG32(DSI_PKT_SEQ_4_HI),
|
|
DEBUGFS_REG32(DSI_PKT_SEQ_5_LO),
|
|
DEBUGFS_REG32(DSI_PKT_SEQ_5_HI),
|
|
DEBUGFS_REG32(DSI_DCS_CMDS),
|
|
DEBUGFS_REG32(DSI_PKT_LEN_0_1),
|
|
DEBUGFS_REG32(DSI_PKT_LEN_2_3),
|
|
DEBUGFS_REG32(DSI_PKT_LEN_4_5),
|
|
DEBUGFS_REG32(DSI_PKT_LEN_6_7),
|
|
DEBUGFS_REG32(DSI_PHY_TIMING_0),
|
|
DEBUGFS_REG32(DSI_PHY_TIMING_1),
|
|
DEBUGFS_REG32(DSI_PHY_TIMING_2),
|
|
DEBUGFS_REG32(DSI_BTA_TIMING),
|
|
DEBUGFS_REG32(DSI_TIMEOUT_0),
|
|
DEBUGFS_REG32(DSI_TIMEOUT_1),
|
|
DEBUGFS_REG32(DSI_TO_TALLY),
|
|
DEBUGFS_REG32(DSI_PAD_CONTROL_0),
|
|
DEBUGFS_REG32(DSI_PAD_CONTROL_CD),
|
|
DEBUGFS_REG32(DSI_PAD_CD_STATUS),
|
|
DEBUGFS_REG32(DSI_VIDEO_MODE_CONTROL),
|
|
DEBUGFS_REG32(DSI_PAD_CONTROL_1),
|
|
DEBUGFS_REG32(DSI_PAD_CONTROL_2),
|
|
DEBUGFS_REG32(DSI_PAD_CONTROL_3),
|
|
DEBUGFS_REG32(DSI_PAD_CONTROL_4),
|
|
DEBUGFS_REG32(DSI_GANGED_MODE_CONTROL),
|
|
DEBUGFS_REG32(DSI_GANGED_MODE_START),
|
|
DEBUGFS_REG32(DSI_GANGED_MODE_SIZE),
|
|
DEBUGFS_REG32(DSI_RAW_DATA_BYTE_COUNT),
|
|
DEBUGFS_REG32(DSI_ULTRA_LOW_POWER_CONTROL),
|
|
DEBUGFS_REG32(DSI_INIT_SEQ_DATA_8),
|
|
DEBUGFS_REG32(DSI_INIT_SEQ_DATA_9),
|
|
DEBUGFS_REG32(DSI_INIT_SEQ_DATA_10),
|
|
DEBUGFS_REG32(DSI_INIT_SEQ_DATA_11),
|
|
DEBUGFS_REG32(DSI_INIT_SEQ_DATA_12),
|
|
DEBUGFS_REG32(DSI_INIT_SEQ_DATA_13),
|
|
DEBUGFS_REG32(DSI_INIT_SEQ_DATA_14),
|
|
DEBUGFS_REG32(DSI_INIT_SEQ_DATA_15),
|
|
};
|
|
|
|
static int tegra_dsi_show_regs(struct seq_file *s, void *data)
|
|
{
|
|
struct drm_info_node *node = s->private;
|
|
struct tegra_dsi *dsi = node->info_ent->data;
|
|
struct drm_crtc *crtc = dsi->output.encoder.crtc;
|
|
struct drm_device *drm = node->minor->dev;
|
|
unsigned int i;
|
|
int err = 0;
|
|
|
|
drm_modeset_lock_all(drm);
|
|
|
|
if (!crtc || !crtc->state->active) {
|
|
err = -EBUSY;
|
|
goto unlock;
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(tegra_dsi_regs); i++) {
|
|
unsigned int offset = tegra_dsi_regs[i].offset;
|
|
|
|
seq_printf(s, "%-32s %#05x %08x\n", tegra_dsi_regs[i].name,
|
|
offset, tegra_dsi_readl(dsi, offset));
|
|
}
|
|
|
|
unlock:
|
|
drm_modeset_unlock_all(drm);
|
|
return err;
|
|
}
|
|
|
|
static struct drm_info_list debugfs_files[] = {
|
|
{ "regs", tegra_dsi_show_regs, 0, NULL },
|
|
};
|
|
|
|
static int tegra_dsi_late_register(struct drm_connector *connector)
|
|
{
|
|
struct tegra_output *output = connector_to_output(connector);
|
|
unsigned int i, count = ARRAY_SIZE(debugfs_files);
|
|
struct drm_minor *minor = connector->dev->primary;
|
|
struct dentry *root = connector->debugfs_entry;
|
|
struct tegra_dsi *dsi = to_dsi(output);
|
|
|
|
dsi->debugfs_files = kmemdup(debugfs_files, sizeof(debugfs_files),
|
|
GFP_KERNEL);
|
|
if (!dsi->debugfs_files)
|
|
return -ENOMEM;
|
|
|
|
for (i = 0; i < count; i++)
|
|
dsi->debugfs_files[i].data = dsi;
|
|
|
|
drm_debugfs_create_files(dsi->debugfs_files, count, root, minor);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void tegra_dsi_early_unregister(struct drm_connector *connector)
|
|
{
|
|
struct tegra_output *output = connector_to_output(connector);
|
|
unsigned int count = ARRAY_SIZE(debugfs_files);
|
|
struct tegra_dsi *dsi = to_dsi(output);
|
|
|
|
drm_debugfs_remove_files(dsi->debugfs_files, count,
|
|
connector->debugfs_entry,
|
|
connector->dev->primary);
|
|
kfree(dsi->debugfs_files);
|
|
dsi->debugfs_files = NULL;
|
|
}
|
|
|
|
#define PKT_ID0(id) ((((id) & 0x3f) << 3) | (1 << 9))
|
|
#define PKT_LEN0(len) (((len) & 0x07) << 0)
|
|
#define PKT_ID1(id) ((((id) & 0x3f) << 13) | (1 << 19))
|
|
#define PKT_LEN1(len) (((len) & 0x07) << 10)
|
|
#define PKT_ID2(id) ((((id) & 0x3f) << 23) | (1 << 29))
|
|
#define PKT_LEN2(len) (((len) & 0x07) << 20)
|
|
|
|
#define PKT_LP (1 << 30)
|
|
#define NUM_PKT_SEQ 12
|
|
|
|
/*
|
|
* non-burst mode with sync pulses
|
|
*/
|
|
static const u32 pkt_seq_video_non_burst_sync_pulses[NUM_PKT_SEQ] = {
|
|
[ 0] = PKT_ID0(MIPI_DSI_V_SYNC_START) | PKT_LEN0(0) |
|
|
PKT_ID1(MIPI_DSI_BLANKING_PACKET) | PKT_LEN1(1) |
|
|
PKT_ID2(MIPI_DSI_H_SYNC_END) | PKT_LEN2(0) |
|
|
PKT_LP,
|
|
[ 1] = 0,
|
|
[ 2] = PKT_ID0(MIPI_DSI_V_SYNC_END) | PKT_LEN0(0) |
|
|
PKT_ID1(MIPI_DSI_BLANKING_PACKET) | PKT_LEN1(1) |
|
|
PKT_ID2(MIPI_DSI_H_SYNC_END) | PKT_LEN2(0) |
|
|
PKT_LP,
|
|
[ 3] = 0,
|
|
[ 4] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) |
|
|
PKT_ID1(MIPI_DSI_BLANKING_PACKET) | PKT_LEN1(1) |
|
|
PKT_ID2(MIPI_DSI_H_SYNC_END) | PKT_LEN2(0) |
|
|
PKT_LP,
|
|
[ 5] = 0,
|
|
[ 6] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) |
|
|
PKT_ID1(MIPI_DSI_BLANKING_PACKET) | PKT_LEN1(1) |
|
|
PKT_ID2(MIPI_DSI_H_SYNC_END) | PKT_LEN2(0),
|
|
[ 7] = PKT_ID0(MIPI_DSI_BLANKING_PACKET) | PKT_LEN0(2) |
|
|
PKT_ID1(MIPI_DSI_PACKED_PIXEL_STREAM_24) | PKT_LEN1(3) |
|
|
PKT_ID2(MIPI_DSI_BLANKING_PACKET) | PKT_LEN2(4),
|
|
[ 8] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) |
|
|
PKT_ID1(MIPI_DSI_BLANKING_PACKET) | PKT_LEN1(1) |
|
|
PKT_ID2(MIPI_DSI_H_SYNC_END) | PKT_LEN2(0) |
|
|
PKT_LP,
|
|
[ 9] = 0,
|
|
[10] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) |
|
|
PKT_ID1(MIPI_DSI_BLANKING_PACKET) | PKT_LEN1(1) |
|
|
PKT_ID2(MIPI_DSI_H_SYNC_END) | PKT_LEN2(0),
|
|
[11] = PKT_ID0(MIPI_DSI_BLANKING_PACKET) | PKT_LEN0(2) |
|
|
PKT_ID1(MIPI_DSI_PACKED_PIXEL_STREAM_24) | PKT_LEN1(3) |
|
|
PKT_ID2(MIPI_DSI_BLANKING_PACKET) | PKT_LEN2(4),
|
|
};
|
|
|
|
/*
|
|
* non-burst mode with sync events
|
|
*/
|
|
static const u32 pkt_seq_video_non_burst_sync_events[NUM_PKT_SEQ] = {
|
|
[ 0] = PKT_ID0(MIPI_DSI_V_SYNC_START) | PKT_LEN0(0) |
|
|
PKT_ID1(MIPI_DSI_END_OF_TRANSMISSION) | PKT_LEN1(7) |
|
|
PKT_LP,
|
|
[ 1] = 0,
|
|
[ 2] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) |
|
|
PKT_ID1(MIPI_DSI_END_OF_TRANSMISSION) | PKT_LEN1(7) |
|
|
PKT_LP,
|
|
[ 3] = 0,
|
|
[ 4] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) |
|
|
PKT_ID1(MIPI_DSI_END_OF_TRANSMISSION) | PKT_LEN1(7) |
|
|
PKT_LP,
|
|
[ 5] = 0,
|
|
[ 6] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) |
|
|
PKT_ID1(MIPI_DSI_BLANKING_PACKET) | PKT_LEN1(2) |
|
|
PKT_ID2(MIPI_DSI_PACKED_PIXEL_STREAM_24) | PKT_LEN2(3),
|
|
[ 7] = PKT_ID0(MIPI_DSI_BLANKING_PACKET) | PKT_LEN0(4),
|
|
[ 8] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) |
|
|
PKT_ID1(MIPI_DSI_END_OF_TRANSMISSION) | PKT_LEN1(7) |
|
|
PKT_LP,
|
|
[ 9] = 0,
|
|
[10] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) |
|
|
PKT_ID1(MIPI_DSI_BLANKING_PACKET) | PKT_LEN1(2) |
|
|
PKT_ID2(MIPI_DSI_PACKED_PIXEL_STREAM_24) | PKT_LEN2(3),
|
|
[11] = PKT_ID0(MIPI_DSI_BLANKING_PACKET) | PKT_LEN0(4),
|
|
};
|
|
|
|
static const u32 pkt_seq_command_mode[NUM_PKT_SEQ] = {
|
|
[ 0] = 0,
|
|
[ 1] = 0,
|
|
[ 2] = 0,
|
|
[ 3] = 0,
|
|
[ 4] = 0,
|
|
[ 5] = 0,
|
|
[ 6] = PKT_ID0(MIPI_DSI_DCS_LONG_WRITE) | PKT_LEN0(3) | PKT_LP,
|
|
[ 7] = 0,
|
|
[ 8] = 0,
|
|
[ 9] = 0,
|
|
[10] = PKT_ID0(MIPI_DSI_DCS_LONG_WRITE) | PKT_LEN0(5) | PKT_LP,
|
|
[11] = 0,
|
|
};
|
|
|
|
static void tegra_dsi_set_phy_timing(struct tegra_dsi *dsi,
|
|
unsigned long period,
|
|
const struct mipi_dphy_timing *timing)
|
|
{
|
|
u32 value;
|
|
|
|
value = DSI_TIMING_FIELD(timing->hsexit, period, 1) << 24 |
|
|
DSI_TIMING_FIELD(timing->hstrail, period, 0) << 16 |
|
|
DSI_TIMING_FIELD(timing->hszero, period, 3) << 8 |
|
|
DSI_TIMING_FIELD(timing->hsprepare, period, 1);
|
|
tegra_dsi_writel(dsi, value, DSI_PHY_TIMING_0);
|
|
|
|
value = DSI_TIMING_FIELD(timing->clktrail, period, 1) << 24 |
|
|
DSI_TIMING_FIELD(timing->clkpost, period, 1) << 16 |
|
|
DSI_TIMING_FIELD(timing->clkzero, period, 1) << 8 |
|
|
DSI_TIMING_FIELD(timing->lpx, period, 1);
|
|
tegra_dsi_writel(dsi, value, DSI_PHY_TIMING_1);
|
|
|
|
value = DSI_TIMING_FIELD(timing->clkprepare, period, 1) << 16 |
|
|
DSI_TIMING_FIELD(timing->clkpre, period, 1) << 8 |
|
|
DSI_TIMING_FIELD(0xff * period, period, 0) << 0;
|
|
tegra_dsi_writel(dsi, value, DSI_PHY_TIMING_2);
|
|
|
|
value = DSI_TIMING_FIELD(timing->taget, period, 1) << 16 |
|
|
DSI_TIMING_FIELD(timing->tasure, period, 1) << 8 |
|
|
DSI_TIMING_FIELD(timing->tago, period, 1);
|
|
tegra_dsi_writel(dsi, value, DSI_BTA_TIMING);
|
|
|
|
if (dsi->slave)
|
|
tegra_dsi_set_phy_timing(dsi->slave, period, timing);
|
|
}
|
|
|
|
static int tegra_dsi_get_muldiv(enum mipi_dsi_pixel_format format,
|
|
unsigned int *mulp, unsigned int *divp)
|
|
{
|
|
switch (format) {
|
|
case MIPI_DSI_FMT_RGB666_PACKED:
|
|
case MIPI_DSI_FMT_RGB888:
|
|
*mulp = 3;
|
|
*divp = 1;
|
|
break;
|
|
|
|
case MIPI_DSI_FMT_RGB565:
|
|
*mulp = 2;
|
|
*divp = 1;
|
|
break;
|
|
|
|
case MIPI_DSI_FMT_RGB666:
|
|
*mulp = 9;
|
|
*divp = 4;
|
|
break;
|
|
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tegra_dsi_get_format(enum mipi_dsi_pixel_format format,
|
|
enum tegra_dsi_format *fmt)
|
|
{
|
|
switch (format) {
|
|
case MIPI_DSI_FMT_RGB888:
|
|
*fmt = TEGRA_DSI_FORMAT_24P;
|
|
break;
|
|
|
|
case MIPI_DSI_FMT_RGB666:
|
|
*fmt = TEGRA_DSI_FORMAT_18NP;
|
|
break;
|
|
|
|
case MIPI_DSI_FMT_RGB666_PACKED:
|
|
*fmt = TEGRA_DSI_FORMAT_18P;
|
|
break;
|
|
|
|
case MIPI_DSI_FMT_RGB565:
|
|
*fmt = TEGRA_DSI_FORMAT_16P;
|
|
break;
|
|
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void tegra_dsi_ganged_enable(struct tegra_dsi *dsi, unsigned int start,
|
|
unsigned int size)
|
|
{
|
|
u32 value;
|
|
|
|
tegra_dsi_writel(dsi, start, DSI_GANGED_MODE_START);
|
|
tegra_dsi_writel(dsi, size << 16 | size, DSI_GANGED_MODE_SIZE);
|
|
|
|
value = DSI_GANGED_MODE_CONTROL_ENABLE;
|
|
tegra_dsi_writel(dsi, value, DSI_GANGED_MODE_CONTROL);
|
|
}
|
|
|
|
static void tegra_dsi_enable(struct tegra_dsi *dsi)
|
|
{
|
|
u32 value;
|
|
|
|
value = tegra_dsi_readl(dsi, DSI_POWER_CONTROL);
|
|
value |= DSI_POWER_CONTROL_ENABLE;
|
|
tegra_dsi_writel(dsi, value, DSI_POWER_CONTROL);
|
|
|
|
if (dsi->slave)
|
|
tegra_dsi_enable(dsi->slave);
|
|
}
|
|
|
|
static unsigned int tegra_dsi_get_lanes(struct tegra_dsi *dsi)
|
|
{
|
|
if (dsi->master)
|
|
return dsi->master->lanes + dsi->lanes;
|
|
|
|
if (dsi->slave)
|
|
return dsi->lanes + dsi->slave->lanes;
|
|
|
|
return dsi->lanes;
|
|
}
|
|
|
|
static void tegra_dsi_configure(struct tegra_dsi *dsi, unsigned int pipe,
|
|
const struct drm_display_mode *mode)
|
|
{
|
|
unsigned int hact, hsw, hbp, hfp, i, mul, div;
|
|
struct tegra_dsi_state *state;
|
|
const u32 *pkt_seq;
|
|
u32 value;
|
|
|
|
/* XXX: pass in state into this function? */
|
|
if (dsi->master)
|
|
state = tegra_dsi_get_state(dsi->master);
|
|
else
|
|
state = tegra_dsi_get_state(dsi);
|
|
|
|
mul = state->mul;
|
|
div = state->div;
|
|
|
|
if (dsi->flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) {
|
|
DRM_DEBUG_KMS("Non-burst video mode with sync pulses\n");
|
|
pkt_seq = pkt_seq_video_non_burst_sync_pulses;
|
|
} else if (dsi->flags & MIPI_DSI_MODE_VIDEO) {
|
|
DRM_DEBUG_KMS("Non-burst video mode with sync events\n");
|
|
pkt_seq = pkt_seq_video_non_burst_sync_events;
|
|
} else {
|
|
DRM_DEBUG_KMS("Command mode\n");
|
|
pkt_seq = pkt_seq_command_mode;
|
|
}
|
|
|
|
value = DSI_CONTROL_CHANNEL(0) |
|
|
DSI_CONTROL_FORMAT(state->format) |
|
|
DSI_CONTROL_LANES(dsi->lanes - 1) |
|
|
DSI_CONTROL_SOURCE(pipe);
|
|
tegra_dsi_writel(dsi, value, DSI_CONTROL);
|
|
|
|
tegra_dsi_writel(dsi, dsi->video_fifo_depth, DSI_MAX_THRESHOLD);
|
|
|
|
value = DSI_HOST_CONTROL_HS;
|
|
tegra_dsi_writel(dsi, value, DSI_HOST_CONTROL);
|
|
|
|
value = tegra_dsi_readl(dsi, DSI_CONTROL);
|
|
|
|
if (dsi->flags & MIPI_DSI_CLOCK_NON_CONTINUOUS)
|
|
value |= DSI_CONTROL_HS_CLK_CTRL;
|
|
|
|
value &= ~DSI_CONTROL_TX_TRIG(3);
|
|
|
|
/* enable DCS commands for command mode */
|
|
if (dsi->flags & MIPI_DSI_MODE_VIDEO)
|
|
value &= ~DSI_CONTROL_DCS_ENABLE;
|
|
else
|
|
value |= DSI_CONTROL_DCS_ENABLE;
|
|
|
|
value |= DSI_CONTROL_VIDEO_ENABLE;
|
|
value &= ~DSI_CONTROL_HOST_ENABLE;
|
|
tegra_dsi_writel(dsi, value, DSI_CONTROL);
|
|
|
|
for (i = 0; i < NUM_PKT_SEQ; i++)
|
|
tegra_dsi_writel(dsi, pkt_seq[i], DSI_PKT_SEQ_0_LO + i);
|
|
|
|
if (dsi->flags & MIPI_DSI_MODE_VIDEO) {
|
|
/* horizontal active pixels */
|
|
hact = mode->hdisplay * mul / div;
|
|
|
|
/* horizontal sync width */
|
|
hsw = (mode->hsync_end - mode->hsync_start) * mul / div;
|
|
|
|
/* horizontal back porch */
|
|
hbp = (mode->htotal - mode->hsync_end) * mul / div;
|
|
|
|
/* horizontal front porch */
|
|
hfp = (mode->hsync_start - mode->hdisplay) * mul / div;
|
|
|
|
if (dsi->master || dsi->slave) {
|
|
hact /= 2;
|
|
hsw /= 2;
|
|
hbp /= 2;
|
|
hfp /= 2;
|
|
}
|
|
|
|
if ((dsi->flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) == 0)
|
|
hbp += hsw;
|
|
|
|
/* subtract packet overhead */
|
|
hsw -= 10;
|
|
hbp -= 14;
|
|
hfp -= 8;
|
|
|
|
tegra_dsi_writel(dsi, hsw << 16 | 0, DSI_PKT_LEN_0_1);
|
|
tegra_dsi_writel(dsi, hact << 16 | hbp, DSI_PKT_LEN_2_3);
|
|
tegra_dsi_writel(dsi, hfp, DSI_PKT_LEN_4_5);
|
|
tegra_dsi_writel(dsi, 0x0f0f << 16, DSI_PKT_LEN_6_7);
|
|
} else {
|
|
u16 bytes;
|
|
|
|
if (dsi->master || dsi->slave) {
|
|
/*
|
|
* For ganged mode, assume symmetric left-right mode.
|
|
*/
|
|
bytes = 1 + (mode->hdisplay / 2) * mul / div;
|
|
} else {
|
|
/* 1 byte (DCS command) + pixel data */
|
|
bytes = 1 + mode->hdisplay * mul / div;
|
|
}
|
|
|
|
tegra_dsi_writel(dsi, 0, DSI_PKT_LEN_0_1);
|
|
tegra_dsi_writel(dsi, bytes << 16, DSI_PKT_LEN_2_3);
|
|
tegra_dsi_writel(dsi, bytes << 16, DSI_PKT_LEN_4_5);
|
|
tegra_dsi_writel(dsi, 0, DSI_PKT_LEN_6_7);
|
|
|
|
value = MIPI_DCS_WRITE_MEMORY_START << 8 |
|
|
MIPI_DCS_WRITE_MEMORY_CONTINUE;
|
|
tegra_dsi_writel(dsi, value, DSI_DCS_CMDS);
|
|
}
|
|
|
|
/* set SOL delay */
|
|
if (dsi->master || dsi->slave) {
|
|
unsigned long delay, bclk, bclk_ganged;
|
|
unsigned int lanes = state->lanes;
|
|
|
|
/* SOL to valid, valid to FIFO and FIFO write delay */
|
|
delay = 4 + 4 + 2;
|
|
delay = DIV_ROUND_UP(delay * mul, div * lanes);
|
|
/* FIFO read delay */
|
|
delay = delay + 6;
|
|
|
|
bclk = DIV_ROUND_UP(mode->htotal * mul, div * lanes);
|
|
bclk_ganged = DIV_ROUND_UP(bclk * lanes / 2, lanes);
|
|
value = bclk - bclk_ganged + delay + 20;
|
|
} else {
|
|
value = 8 * mul / div;
|
|
}
|
|
|
|
tegra_dsi_writel(dsi, value, DSI_SOL_DELAY);
|
|
|
|
if (dsi->slave) {
|
|
tegra_dsi_configure(dsi->slave, pipe, mode);
|
|
|
|
/*
|
|
* TODO: Support modes other than symmetrical left-right
|
|
* split.
|
|
*/
|
|
tegra_dsi_ganged_enable(dsi, 0, mode->hdisplay / 2);
|
|
tegra_dsi_ganged_enable(dsi->slave, mode->hdisplay / 2,
|
|
mode->hdisplay / 2);
|
|
}
|
|
}
|
|
|
|
static int tegra_dsi_wait_idle(struct tegra_dsi *dsi, unsigned long timeout)
|
|
{
|
|
u32 value;
|
|
|
|
timeout = jiffies + msecs_to_jiffies(timeout);
|
|
|
|
while (time_before(jiffies, timeout)) {
|
|
value = tegra_dsi_readl(dsi, DSI_STATUS);
|
|
if (value & DSI_STATUS_IDLE)
|
|
return 0;
|
|
|
|
usleep_range(1000, 2000);
|
|
}
|
|
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
static void tegra_dsi_video_disable(struct tegra_dsi *dsi)
|
|
{
|
|
u32 value;
|
|
|
|
value = tegra_dsi_readl(dsi, DSI_CONTROL);
|
|
value &= ~DSI_CONTROL_VIDEO_ENABLE;
|
|
tegra_dsi_writel(dsi, value, DSI_CONTROL);
|
|
|
|
if (dsi->slave)
|
|
tegra_dsi_video_disable(dsi->slave);
|
|
}
|
|
|
|
static void tegra_dsi_ganged_disable(struct tegra_dsi *dsi)
|
|
{
|
|
tegra_dsi_writel(dsi, 0, DSI_GANGED_MODE_START);
|
|
tegra_dsi_writel(dsi, 0, DSI_GANGED_MODE_SIZE);
|
|
tegra_dsi_writel(dsi, 0, DSI_GANGED_MODE_CONTROL);
|
|
}
|
|
|
|
static int tegra_dsi_pad_enable(struct tegra_dsi *dsi)
|
|
{
|
|
u32 value;
|
|
|
|
value = DSI_PAD_CONTROL_VS1_PULLDN(0) | DSI_PAD_CONTROL_VS1_PDIO(0);
|
|
tegra_dsi_writel(dsi, value, DSI_PAD_CONTROL_0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tegra_dsi_pad_calibrate(struct tegra_dsi *dsi)
|
|
{
|
|
u32 value;
|
|
int err;
|
|
|
|
/*
|
|
* XXX Is this still needed? The module reset is deasserted right
|
|
* before this function is called.
|
|
*/
|
|
tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_0);
|
|
tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_1);
|
|
tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_2);
|
|
tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_3);
|
|
tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_4);
|
|
|
|
/* start calibration */
|
|
tegra_dsi_pad_enable(dsi);
|
|
|
|
value = DSI_PAD_SLEW_UP(0x7) | DSI_PAD_SLEW_DN(0x7) |
|
|
DSI_PAD_LP_UP(0x1) | DSI_PAD_LP_DN(0x1) |
|
|
DSI_PAD_OUT_CLK(0x0);
|
|
tegra_dsi_writel(dsi, value, DSI_PAD_CONTROL_2);
|
|
|
|
value = DSI_PAD_PREEMP_PD_CLK(0x3) | DSI_PAD_PREEMP_PU_CLK(0x3) |
|
|
DSI_PAD_PREEMP_PD(0x03) | DSI_PAD_PREEMP_PU(0x3);
|
|
tegra_dsi_writel(dsi, value, DSI_PAD_CONTROL_3);
|
|
|
|
err = tegra_mipi_start_calibration(dsi->mipi);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
return tegra_mipi_finish_calibration(dsi->mipi);
|
|
}
|
|
|
|
static void tegra_dsi_set_timeout(struct tegra_dsi *dsi, unsigned long bclk,
|
|
unsigned int vrefresh)
|
|
{
|
|
unsigned int timeout;
|
|
u32 value;
|
|
|
|
/* one frame high-speed transmission timeout */
|
|
timeout = (bclk / vrefresh) / 512;
|
|
value = DSI_TIMEOUT_LRX(0x2000) | DSI_TIMEOUT_HTX(timeout);
|
|
tegra_dsi_writel(dsi, value, DSI_TIMEOUT_0);
|
|
|
|
/* 2 ms peripheral timeout for panel */
|
|
timeout = 2 * bclk / 512 * 1000;
|
|
value = DSI_TIMEOUT_PR(timeout) | DSI_TIMEOUT_TA(0x2000);
|
|
tegra_dsi_writel(dsi, value, DSI_TIMEOUT_1);
|
|
|
|
value = DSI_TALLY_TA(0) | DSI_TALLY_LRX(0) | DSI_TALLY_HTX(0);
|
|
tegra_dsi_writel(dsi, value, DSI_TO_TALLY);
|
|
|
|
if (dsi->slave)
|
|
tegra_dsi_set_timeout(dsi->slave, bclk, vrefresh);
|
|
}
|
|
|
|
static void tegra_dsi_disable(struct tegra_dsi *dsi)
|
|
{
|
|
u32 value;
|
|
|
|
if (dsi->slave) {
|
|
tegra_dsi_ganged_disable(dsi->slave);
|
|
tegra_dsi_ganged_disable(dsi);
|
|
}
|
|
|
|
value = tegra_dsi_readl(dsi, DSI_POWER_CONTROL);
|
|
value &= ~DSI_POWER_CONTROL_ENABLE;
|
|
tegra_dsi_writel(dsi, value, DSI_POWER_CONTROL);
|
|
|
|
if (dsi->slave)
|
|
tegra_dsi_disable(dsi->slave);
|
|
|
|
usleep_range(5000, 10000);
|
|
}
|
|
|
|
static void tegra_dsi_soft_reset(struct tegra_dsi *dsi)
|
|
{
|
|
u32 value;
|
|
|
|
value = tegra_dsi_readl(dsi, DSI_POWER_CONTROL);
|
|
value &= ~DSI_POWER_CONTROL_ENABLE;
|
|
tegra_dsi_writel(dsi, value, DSI_POWER_CONTROL);
|
|
|
|
usleep_range(300, 1000);
|
|
|
|
value = tegra_dsi_readl(dsi, DSI_POWER_CONTROL);
|
|
value |= DSI_POWER_CONTROL_ENABLE;
|
|
tegra_dsi_writel(dsi, value, DSI_POWER_CONTROL);
|
|
|
|
usleep_range(300, 1000);
|
|
|
|
value = tegra_dsi_readl(dsi, DSI_TRIGGER);
|
|
if (value)
|
|
tegra_dsi_writel(dsi, 0, DSI_TRIGGER);
|
|
|
|
if (dsi->slave)
|
|
tegra_dsi_soft_reset(dsi->slave);
|
|
}
|
|
|
|
static void tegra_dsi_connector_reset(struct drm_connector *connector)
|
|
{
|
|
struct tegra_dsi_state *state = kzalloc(sizeof(*state), GFP_KERNEL);
|
|
|
|
if (!state)
|
|
return;
|
|
|
|
if (connector->state) {
|
|
__drm_atomic_helper_connector_destroy_state(connector->state);
|
|
kfree(connector->state);
|
|
}
|
|
|
|
__drm_atomic_helper_connector_reset(connector, &state->base);
|
|
}
|
|
|
|
static struct drm_connector_state *
|
|
tegra_dsi_connector_duplicate_state(struct drm_connector *connector)
|
|
{
|
|
struct tegra_dsi_state *state = to_dsi_state(connector->state);
|
|
struct tegra_dsi_state *copy;
|
|
|
|
copy = kmemdup(state, sizeof(*state), GFP_KERNEL);
|
|
if (!copy)
|
|
return NULL;
|
|
|
|
__drm_atomic_helper_connector_duplicate_state(connector,
|
|
©->base);
|
|
|
|
return ©->base;
|
|
}
|
|
|
|
static const struct drm_connector_funcs tegra_dsi_connector_funcs = {
|
|
.reset = tegra_dsi_connector_reset,
|
|
.detect = tegra_output_connector_detect,
|
|
.fill_modes = drm_helper_probe_single_connector_modes,
|
|
.destroy = tegra_output_connector_destroy,
|
|
.atomic_duplicate_state = tegra_dsi_connector_duplicate_state,
|
|
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
|
|
.late_register = tegra_dsi_late_register,
|
|
.early_unregister = tegra_dsi_early_unregister,
|
|
};
|
|
|
|
static enum drm_mode_status
|
|
tegra_dsi_connector_mode_valid(struct drm_connector *connector,
|
|
const struct drm_display_mode *mode)
|
|
{
|
|
return MODE_OK;
|
|
}
|
|
|
|
static const struct drm_connector_helper_funcs tegra_dsi_connector_helper_funcs = {
|
|
.get_modes = tegra_output_connector_get_modes,
|
|
.mode_valid = tegra_dsi_connector_mode_valid,
|
|
};
|
|
|
|
static void tegra_dsi_unprepare(struct tegra_dsi *dsi)
|
|
{
|
|
int err;
|
|
|
|
if (dsi->slave)
|
|
tegra_dsi_unprepare(dsi->slave);
|
|
|
|
err = tegra_mipi_disable(dsi->mipi);
|
|
if (err < 0)
|
|
dev_err(dsi->dev, "failed to disable MIPI calibration: %d\n",
|
|
err);
|
|
|
|
err = host1x_client_suspend(&dsi->client);
|
|
if (err < 0)
|
|
dev_err(dsi->dev, "failed to suspend: %d\n", err);
|
|
}
|
|
|
|
static void tegra_dsi_encoder_disable(struct drm_encoder *encoder)
|
|
{
|
|
struct tegra_output *output = encoder_to_output(encoder);
|
|
struct tegra_dc *dc = to_tegra_dc(encoder->crtc);
|
|
struct tegra_dsi *dsi = to_dsi(output);
|
|
u32 value;
|
|
int err;
|
|
|
|
if (output->panel)
|
|
drm_panel_disable(output->panel);
|
|
|
|
tegra_dsi_video_disable(dsi);
|
|
|
|
/*
|
|
* The following accesses registers of the display controller, so make
|
|
* sure it's only executed when the output is attached to one.
|
|
*/
|
|
if (dc) {
|
|
value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS);
|
|
value &= ~DSI_ENABLE;
|
|
tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS);
|
|
|
|
tegra_dc_commit(dc);
|
|
}
|
|
|
|
err = tegra_dsi_wait_idle(dsi, 100);
|
|
if (err < 0)
|
|
dev_dbg(dsi->dev, "failed to idle DSI: %d\n", err);
|
|
|
|
tegra_dsi_soft_reset(dsi);
|
|
|
|
if (output->panel)
|
|
drm_panel_unprepare(output->panel);
|
|
|
|
tegra_dsi_disable(dsi);
|
|
|
|
tegra_dsi_unprepare(dsi);
|
|
}
|
|
|
|
static int tegra_dsi_prepare(struct tegra_dsi *dsi)
|
|
{
|
|
int err;
|
|
|
|
err = host1x_client_resume(&dsi->client);
|
|
if (err < 0) {
|
|
dev_err(dsi->dev, "failed to resume: %d\n", err);
|
|
return err;
|
|
}
|
|
|
|
err = tegra_mipi_enable(dsi->mipi);
|
|
if (err < 0)
|
|
dev_err(dsi->dev, "failed to enable MIPI calibration: %d\n",
|
|
err);
|
|
|
|
err = tegra_dsi_pad_calibrate(dsi);
|
|
if (err < 0)
|
|
dev_err(dsi->dev, "MIPI calibration failed: %d\n", err);
|
|
|
|
if (dsi->slave)
|
|
tegra_dsi_prepare(dsi->slave);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void tegra_dsi_encoder_enable(struct drm_encoder *encoder)
|
|
{
|
|
struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode;
|
|
struct tegra_output *output = encoder_to_output(encoder);
|
|
struct tegra_dc *dc = to_tegra_dc(encoder->crtc);
|
|
struct tegra_dsi *dsi = to_dsi(output);
|
|
struct tegra_dsi_state *state;
|
|
u32 value;
|
|
int err;
|
|
|
|
err = tegra_dsi_prepare(dsi);
|
|
if (err < 0) {
|
|
dev_err(dsi->dev, "failed to prepare: %d\n", err);
|
|
return;
|
|
}
|
|
|
|
state = tegra_dsi_get_state(dsi);
|
|
|
|
tegra_dsi_set_timeout(dsi, state->bclk, state->vrefresh);
|
|
|
|
/*
|
|
* The D-PHY timing fields are expressed in byte-clock cycles, so
|
|
* multiply the period by 8.
|
|
*/
|
|
tegra_dsi_set_phy_timing(dsi, state->period * 8, &state->timing);
|
|
|
|
if (output->panel)
|
|
drm_panel_prepare(output->panel);
|
|
|
|
tegra_dsi_configure(dsi, dc->pipe, mode);
|
|
|
|
/* enable display controller */
|
|
value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS);
|
|
value |= DSI_ENABLE;
|
|
tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS);
|
|
|
|
tegra_dc_commit(dc);
|
|
|
|
/* enable DSI controller */
|
|
tegra_dsi_enable(dsi);
|
|
|
|
if (output->panel)
|
|
drm_panel_enable(output->panel);
|
|
}
|
|
|
|
static int
|
|
tegra_dsi_encoder_atomic_check(struct drm_encoder *encoder,
|
|
struct drm_crtc_state *crtc_state,
|
|
struct drm_connector_state *conn_state)
|
|
{
|
|
struct tegra_output *output = encoder_to_output(encoder);
|
|
struct tegra_dsi_state *state = to_dsi_state(conn_state);
|
|
struct tegra_dc *dc = to_tegra_dc(conn_state->crtc);
|
|
struct tegra_dsi *dsi = to_dsi(output);
|
|
unsigned int scdiv;
|
|
unsigned long plld;
|
|
int err;
|
|
|
|
state->pclk = crtc_state->mode.clock * 1000;
|
|
|
|
err = tegra_dsi_get_muldiv(dsi->format, &state->mul, &state->div);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
state->lanes = tegra_dsi_get_lanes(dsi);
|
|
|
|
err = tegra_dsi_get_format(dsi->format, &state->format);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
state->vrefresh = drm_mode_vrefresh(&crtc_state->mode);
|
|
|
|
/* compute byte clock */
|
|
state->bclk = (state->pclk * state->mul) / (state->div * state->lanes);
|
|
|
|
DRM_DEBUG_KMS("mul: %u, div: %u, lanes: %u\n", state->mul, state->div,
|
|
state->lanes);
|
|
DRM_DEBUG_KMS("format: %u, vrefresh: %u\n", state->format,
|
|
state->vrefresh);
|
|
DRM_DEBUG_KMS("bclk: %lu\n", state->bclk);
|
|
|
|
/*
|
|
* Compute bit clock and round up to the next MHz.
|
|
*/
|
|
plld = DIV_ROUND_UP(state->bclk * 8, USEC_PER_SEC) * USEC_PER_SEC;
|
|
state->period = DIV_ROUND_CLOSEST(NSEC_PER_SEC, plld);
|
|
|
|
err = mipi_dphy_timing_get_default(&state->timing, state->period);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
err = mipi_dphy_timing_validate(&state->timing, state->period);
|
|
if (err < 0) {
|
|
dev_err(dsi->dev, "failed to validate D-PHY timing: %d\n", err);
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* We divide the frequency by two here, but we make up for that by
|
|
* setting the shift clock divider (further below) to half of the
|
|
* correct value.
|
|
*/
|
|
plld /= 2;
|
|
|
|
/*
|
|
* Derive pixel clock from bit clock using the shift clock divider.
|
|
* Note that this is only half of what we would expect, but we need
|
|
* that to make up for the fact that we divided the bit clock by a
|
|
* factor of two above.
|
|
*
|
|
* It's not clear exactly why this is necessary, but the display is
|
|
* not working properly otherwise. Perhaps the PLLs cannot generate
|
|
* frequencies sufficiently high.
|
|
*/
|
|
scdiv = ((8 * state->mul) / (state->div * state->lanes)) - 2;
|
|
|
|
err = tegra_dc_state_setup_clock(dc, crtc_state, dsi->clk_parent,
|
|
plld, scdiv);
|
|
if (err < 0) {
|
|
dev_err(output->dev, "failed to setup CRTC state: %d\n", err);
|
|
return err;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static const struct drm_encoder_helper_funcs tegra_dsi_encoder_helper_funcs = {
|
|
.disable = tegra_dsi_encoder_disable,
|
|
.enable = tegra_dsi_encoder_enable,
|
|
.atomic_check = tegra_dsi_encoder_atomic_check,
|
|
};
|
|
|
|
static int tegra_dsi_init(struct host1x_client *client)
|
|
{
|
|
struct drm_device *drm = dev_get_drvdata(client->host);
|
|
struct tegra_dsi *dsi = host1x_client_to_dsi(client);
|
|
int err;
|
|
|
|
/* Gangsters must not register their own outputs. */
|
|
if (!dsi->master) {
|
|
dsi->output.dev = client->dev;
|
|
|
|
drm_connector_init(drm, &dsi->output.connector,
|
|
&tegra_dsi_connector_funcs,
|
|
DRM_MODE_CONNECTOR_DSI);
|
|
drm_connector_helper_add(&dsi->output.connector,
|
|
&tegra_dsi_connector_helper_funcs);
|
|
dsi->output.connector.dpms = DRM_MODE_DPMS_OFF;
|
|
|
|
drm_simple_encoder_init(drm, &dsi->output.encoder,
|
|
DRM_MODE_ENCODER_DSI);
|
|
drm_encoder_helper_add(&dsi->output.encoder,
|
|
&tegra_dsi_encoder_helper_funcs);
|
|
|
|
drm_connector_attach_encoder(&dsi->output.connector,
|
|
&dsi->output.encoder);
|
|
drm_connector_register(&dsi->output.connector);
|
|
|
|
err = tegra_output_init(drm, &dsi->output);
|
|
if (err < 0)
|
|
dev_err(dsi->dev, "failed to initialize output: %d\n",
|
|
err);
|
|
|
|
dsi->output.encoder.possible_crtcs = 0x3;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tegra_dsi_exit(struct host1x_client *client)
|
|
{
|
|
struct tegra_dsi *dsi = host1x_client_to_dsi(client);
|
|
|
|
tegra_output_exit(&dsi->output);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tegra_dsi_runtime_suspend(struct host1x_client *client)
|
|
{
|
|
struct tegra_dsi *dsi = host1x_client_to_dsi(client);
|
|
struct device *dev = client->dev;
|
|
int err;
|
|
|
|
if (dsi->rst) {
|
|
err = reset_control_assert(dsi->rst);
|
|
if (err < 0) {
|
|
dev_err(dev, "failed to assert reset: %d\n", err);
|
|
return err;
|
|
}
|
|
}
|
|
|
|
usleep_range(1000, 2000);
|
|
|
|
clk_disable_unprepare(dsi->clk_lp);
|
|
clk_disable_unprepare(dsi->clk);
|
|
|
|
regulator_disable(dsi->vdd);
|
|
pm_runtime_put_sync(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tegra_dsi_runtime_resume(struct host1x_client *client)
|
|
{
|
|
struct tegra_dsi *dsi = host1x_client_to_dsi(client);
|
|
struct device *dev = client->dev;
|
|
int err;
|
|
|
|
err = pm_runtime_resume_and_get(dev);
|
|
if (err < 0) {
|
|
dev_err(dev, "failed to get runtime PM: %d\n", err);
|
|
return err;
|
|
}
|
|
|
|
err = regulator_enable(dsi->vdd);
|
|
if (err < 0) {
|
|
dev_err(dev, "failed to enable VDD supply: %d\n", err);
|
|
goto put_rpm;
|
|
}
|
|
|
|
err = clk_prepare_enable(dsi->clk);
|
|
if (err < 0) {
|
|
dev_err(dev, "cannot enable DSI clock: %d\n", err);
|
|
goto disable_vdd;
|
|
}
|
|
|
|
err = clk_prepare_enable(dsi->clk_lp);
|
|
if (err < 0) {
|
|
dev_err(dev, "cannot enable low-power clock: %d\n", err);
|
|
goto disable_clk;
|
|
}
|
|
|
|
usleep_range(1000, 2000);
|
|
|
|
if (dsi->rst) {
|
|
err = reset_control_deassert(dsi->rst);
|
|
if (err < 0) {
|
|
dev_err(dev, "cannot assert reset: %d\n", err);
|
|
goto disable_clk_lp;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
disable_clk_lp:
|
|
clk_disable_unprepare(dsi->clk_lp);
|
|
disable_clk:
|
|
clk_disable_unprepare(dsi->clk);
|
|
disable_vdd:
|
|
regulator_disable(dsi->vdd);
|
|
put_rpm:
|
|
pm_runtime_put_sync(dev);
|
|
return err;
|
|
}
|
|
|
|
static const struct host1x_client_ops dsi_client_ops = {
|
|
.init = tegra_dsi_init,
|
|
.exit = tegra_dsi_exit,
|
|
.suspend = tegra_dsi_runtime_suspend,
|
|
.resume = tegra_dsi_runtime_resume,
|
|
};
|
|
|
|
static int tegra_dsi_setup_clocks(struct tegra_dsi *dsi)
|
|
{
|
|
struct clk *parent;
|
|
int err;
|
|
|
|
parent = clk_get_parent(dsi->clk);
|
|
if (!parent)
|
|
return -EINVAL;
|
|
|
|
err = clk_set_parent(parent, dsi->clk_parent);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const char * const error_report[16] = {
|
|
"SoT Error",
|
|
"SoT Sync Error",
|
|
"EoT Sync Error",
|
|
"Escape Mode Entry Command Error",
|
|
"Low-Power Transmit Sync Error",
|
|
"Peripheral Timeout Error",
|
|
"False Control Error",
|
|
"Contention Detected",
|
|
"ECC Error, single-bit",
|
|
"ECC Error, multi-bit",
|
|
"Checksum Error",
|
|
"DSI Data Type Not Recognized",
|
|
"DSI VC ID Invalid",
|
|
"Invalid Transmission Length",
|
|
"Reserved",
|
|
"DSI Protocol Violation",
|
|
};
|
|
|
|
static ssize_t tegra_dsi_read_response(struct tegra_dsi *dsi,
|
|
const struct mipi_dsi_msg *msg,
|
|
size_t count)
|
|
{
|
|
u8 *rx = msg->rx_buf;
|
|
unsigned int i, j, k;
|
|
size_t size = 0;
|
|
u16 errors;
|
|
u32 value;
|
|
|
|
/* read and parse packet header */
|
|
value = tegra_dsi_readl(dsi, DSI_RD_DATA);
|
|
|
|
switch (value & 0x3f) {
|
|
case MIPI_DSI_RX_ACKNOWLEDGE_AND_ERROR_REPORT:
|
|
errors = (value >> 8) & 0xffff;
|
|
dev_dbg(dsi->dev, "Acknowledge and error report: %04x\n",
|
|
errors);
|
|
for (i = 0; i < ARRAY_SIZE(error_report); i++)
|
|
if (errors & BIT(i))
|
|
dev_dbg(dsi->dev, " %2u: %s\n", i,
|
|
error_report[i]);
|
|
break;
|
|
|
|
case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_1BYTE:
|
|
rx[0] = (value >> 8) & 0xff;
|
|
size = 1;
|
|
break;
|
|
|
|
case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_2BYTE:
|
|
rx[0] = (value >> 8) & 0xff;
|
|
rx[1] = (value >> 16) & 0xff;
|
|
size = 2;
|
|
break;
|
|
|
|
case MIPI_DSI_RX_DCS_LONG_READ_RESPONSE:
|
|
size = ((value >> 8) & 0xff00) | ((value >> 8) & 0xff);
|
|
break;
|
|
|
|
case MIPI_DSI_RX_GENERIC_LONG_READ_RESPONSE:
|
|
size = ((value >> 8) & 0xff00) | ((value >> 8) & 0xff);
|
|
break;
|
|
|
|
default:
|
|
dev_err(dsi->dev, "unhandled response type: %02x\n",
|
|
value & 0x3f);
|
|
return -EPROTO;
|
|
}
|
|
|
|
size = min(size, msg->rx_len);
|
|
|
|
if (msg->rx_buf && size > 0) {
|
|
for (i = 0, j = 0; i < count - 1; i++, j += 4) {
|
|
u8 *rx = msg->rx_buf + j;
|
|
|
|
value = tegra_dsi_readl(dsi, DSI_RD_DATA);
|
|
|
|
for (k = 0; k < 4 && (j + k) < msg->rx_len; k++)
|
|
rx[j + k] = (value >> (k << 3)) & 0xff;
|
|
}
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
static int tegra_dsi_transmit(struct tegra_dsi *dsi, unsigned long timeout)
|
|
{
|
|
tegra_dsi_writel(dsi, DSI_TRIGGER_HOST, DSI_TRIGGER);
|
|
|
|
timeout = jiffies + msecs_to_jiffies(timeout);
|
|
|
|
while (time_before(jiffies, timeout)) {
|
|
u32 value = tegra_dsi_readl(dsi, DSI_TRIGGER);
|
|
if ((value & DSI_TRIGGER_HOST) == 0)
|
|
return 0;
|
|
|
|
usleep_range(1000, 2000);
|
|
}
|
|
|
|
DRM_DEBUG_KMS("timeout waiting for transmission to complete\n");
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
static int tegra_dsi_wait_for_response(struct tegra_dsi *dsi,
|
|
unsigned long timeout)
|
|
{
|
|
timeout = jiffies + msecs_to_jiffies(250);
|
|
|
|
while (time_before(jiffies, timeout)) {
|
|
u32 value = tegra_dsi_readl(dsi, DSI_STATUS);
|
|
u8 count = value & 0x1f;
|
|
|
|
if (count > 0)
|
|
return count;
|
|
|
|
usleep_range(1000, 2000);
|
|
}
|
|
|
|
DRM_DEBUG_KMS("peripheral returned no data\n");
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
static void tegra_dsi_writesl(struct tegra_dsi *dsi, unsigned long offset,
|
|
const void *buffer, size_t size)
|
|
{
|
|
const u8 *buf = buffer;
|
|
size_t i, j;
|
|
u32 value;
|
|
|
|
for (j = 0; j < size; j += 4) {
|
|
value = 0;
|
|
|
|
for (i = 0; i < 4 && j + i < size; i++)
|
|
value |= buf[j + i] << (i << 3);
|
|
|
|
tegra_dsi_writel(dsi, value, DSI_WR_DATA);
|
|
}
|
|
}
|
|
|
|
static ssize_t tegra_dsi_host_transfer(struct mipi_dsi_host *host,
|
|
const struct mipi_dsi_msg *msg)
|
|
{
|
|
struct tegra_dsi *dsi = host_to_tegra(host);
|
|
struct mipi_dsi_packet packet;
|
|
const u8 *header;
|
|
size_t count;
|
|
ssize_t err;
|
|
u32 value;
|
|
|
|
err = mipi_dsi_create_packet(&packet, msg);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
header = packet.header;
|
|
|
|
/* maximum FIFO depth is 1920 words */
|
|
if (packet.size > dsi->video_fifo_depth * 4)
|
|
return -ENOSPC;
|
|
|
|
/* reset underflow/overflow flags */
|
|
value = tegra_dsi_readl(dsi, DSI_STATUS);
|
|
if (value & (DSI_STATUS_UNDERFLOW | DSI_STATUS_OVERFLOW)) {
|
|
value = DSI_HOST_CONTROL_FIFO_RESET;
|
|
tegra_dsi_writel(dsi, value, DSI_HOST_CONTROL);
|
|
usleep_range(10, 20);
|
|
}
|
|
|
|
value = tegra_dsi_readl(dsi, DSI_POWER_CONTROL);
|
|
value |= DSI_POWER_CONTROL_ENABLE;
|
|
tegra_dsi_writel(dsi, value, DSI_POWER_CONTROL);
|
|
|
|
usleep_range(5000, 10000);
|
|
|
|
value = DSI_HOST_CONTROL_CRC_RESET | DSI_HOST_CONTROL_TX_TRIG_HOST |
|
|
DSI_HOST_CONTROL_CS | DSI_HOST_CONTROL_ECC;
|
|
|
|
if ((msg->flags & MIPI_DSI_MSG_USE_LPM) == 0)
|
|
value |= DSI_HOST_CONTROL_HS;
|
|
|
|
/*
|
|
* The host FIFO has a maximum of 64 words, so larger transmissions
|
|
* need to use the video FIFO.
|
|
*/
|
|
if (packet.size > dsi->host_fifo_depth * 4)
|
|
value |= DSI_HOST_CONTROL_FIFO_SEL;
|
|
|
|
tegra_dsi_writel(dsi, value, DSI_HOST_CONTROL);
|
|
|
|
/*
|
|
* For reads and messages with explicitly requested ACK, generate a
|
|
* BTA sequence after the transmission of the packet.
|
|
*/
|
|
if ((msg->flags & MIPI_DSI_MSG_REQ_ACK) ||
|
|
(msg->rx_buf && msg->rx_len > 0)) {
|
|
value = tegra_dsi_readl(dsi, DSI_HOST_CONTROL);
|
|
value |= DSI_HOST_CONTROL_PKT_BTA;
|
|
tegra_dsi_writel(dsi, value, DSI_HOST_CONTROL);
|
|
}
|
|
|
|
value = DSI_CONTROL_LANES(0) | DSI_CONTROL_HOST_ENABLE;
|
|
tegra_dsi_writel(dsi, value, DSI_CONTROL);
|
|
|
|
/* write packet header, ECC is generated by hardware */
|
|
value = header[2] << 16 | header[1] << 8 | header[0];
|
|
tegra_dsi_writel(dsi, value, DSI_WR_DATA);
|
|
|
|
/* write payload (if any) */
|
|
if (packet.payload_length > 0)
|
|
tegra_dsi_writesl(dsi, DSI_WR_DATA, packet.payload,
|
|
packet.payload_length);
|
|
|
|
err = tegra_dsi_transmit(dsi, 250);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
if ((msg->flags & MIPI_DSI_MSG_REQ_ACK) ||
|
|
(msg->rx_buf && msg->rx_len > 0)) {
|
|
err = tegra_dsi_wait_for_response(dsi, 250);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
count = err;
|
|
|
|
value = tegra_dsi_readl(dsi, DSI_RD_DATA);
|
|
switch (value) {
|
|
case 0x84:
|
|
/*
|
|
dev_dbg(dsi->dev, "ACK\n");
|
|
*/
|
|
break;
|
|
|
|
case 0x87:
|
|
/*
|
|
dev_dbg(dsi->dev, "ESCAPE\n");
|
|
*/
|
|
break;
|
|
|
|
default:
|
|
dev_err(dsi->dev, "unknown status: %08x\n", value);
|
|
break;
|
|
}
|
|
|
|
if (count > 1) {
|
|
err = tegra_dsi_read_response(dsi, msg, count);
|
|
if (err < 0)
|
|
dev_err(dsi->dev,
|
|
"failed to parse response: %zd\n",
|
|
err);
|
|
else {
|
|
/*
|
|
* For read commands, return the number of
|
|
* bytes returned by the peripheral.
|
|
*/
|
|
count = err;
|
|
}
|
|
}
|
|
} else {
|
|
/*
|
|
* For write commands, we have transmitted the 4-byte header
|
|
* plus the variable-length payload.
|
|
*/
|
|
count = 4 + packet.payload_length;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
static int tegra_dsi_ganged_setup(struct tegra_dsi *dsi)
|
|
{
|
|
struct clk *parent;
|
|
int err;
|
|
|
|
/* make sure both DSI controllers share the same PLL */
|
|
parent = clk_get_parent(dsi->slave->clk);
|
|
if (!parent)
|
|
return -EINVAL;
|
|
|
|
err = clk_set_parent(parent, dsi->clk_parent);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tegra_dsi_host_attach(struct mipi_dsi_host *host,
|
|
struct mipi_dsi_device *device)
|
|
{
|
|
struct tegra_dsi *dsi = host_to_tegra(host);
|
|
|
|
dsi->flags = device->mode_flags;
|
|
dsi->format = device->format;
|
|
dsi->lanes = device->lanes;
|
|
|
|
if (dsi->slave) {
|
|
int err;
|
|
|
|
dev_dbg(dsi->dev, "attaching dual-channel device %s\n",
|
|
dev_name(&device->dev));
|
|
|
|
err = tegra_dsi_ganged_setup(dsi);
|
|
if (err < 0) {
|
|
dev_err(dsi->dev, "failed to set up ganged mode: %d\n",
|
|
err);
|
|
return err;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Slaves don't have a panel associated with them, so they provide
|
|
* merely the second channel.
|
|
*/
|
|
if (!dsi->master) {
|
|
struct tegra_output *output = &dsi->output;
|
|
|
|
output->panel = of_drm_find_panel(device->dev.of_node);
|
|
if (IS_ERR(output->panel))
|
|
output->panel = NULL;
|
|
|
|
if (output->panel && output->connector.dev)
|
|
drm_helper_hpd_irq_event(output->connector.dev);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tegra_dsi_host_detach(struct mipi_dsi_host *host,
|
|
struct mipi_dsi_device *device)
|
|
{
|
|
struct tegra_dsi *dsi = host_to_tegra(host);
|
|
struct tegra_output *output = &dsi->output;
|
|
|
|
if (output->panel && &device->dev == output->panel->dev) {
|
|
output->panel = NULL;
|
|
|
|
if (output->connector.dev)
|
|
drm_helper_hpd_irq_event(output->connector.dev);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct mipi_dsi_host_ops tegra_dsi_host_ops = {
|
|
.attach = tegra_dsi_host_attach,
|
|
.detach = tegra_dsi_host_detach,
|
|
.transfer = tegra_dsi_host_transfer,
|
|
};
|
|
|
|
static int tegra_dsi_ganged_probe(struct tegra_dsi *dsi)
|
|
{
|
|
struct device_node *np;
|
|
|
|
np = of_parse_phandle(dsi->dev->of_node, "nvidia,ganged-mode", 0);
|
|
if (np) {
|
|
struct platform_device *gangster = of_find_device_by_node(np);
|
|
of_node_put(np);
|
|
if (!gangster)
|
|
return -EPROBE_DEFER;
|
|
|
|
dsi->slave = platform_get_drvdata(gangster);
|
|
|
|
if (!dsi->slave) {
|
|
put_device(&gangster->dev);
|
|
return -EPROBE_DEFER;
|
|
}
|
|
|
|
dsi->slave->master = dsi;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tegra_dsi_probe(struct platform_device *pdev)
|
|
{
|
|
struct tegra_dsi *dsi;
|
|
int err;
|
|
|
|
dsi = devm_kzalloc(&pdev->dev, sizeof(*dsi), GFP_KERNEL);
|
|
if (!dsi)
|
|
return -ENOMEM;
|
|
|
|
dsi->output.dev = dsi->dev = &pdev->dev;
|
|
dsi->video_fifo_depth = 1920;
|
|
dsi->host_fifo_depth = 64;
|
|
|
|
err = tegra_dsi_ganged_probe(dsi);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
err = tegra_output_probe(&dsi->output);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
dsi->output.connector.polled = DRM_CONNECTOR_POLL_HPD;
|
|
|
|
/*
|
|
* Assume these values by default. When a DSI peripheral driver
|
|
* attaches to the DSI host, the parameters will be taken from
|
|
* the attached device.
|
|
*/
|
|
dsi->flags = MIPI_DSI_MODE_VIDEO;
|
|
dsi->format = MIPI_DSI_FMT_RGB888;
|
|
dsi->lanes = 4;
|
|
|
|
if (!pdev->dev.pm_domain) {
|
|
dsi->rst = devm_reset_control_get(&pdev->dev, "dsi");
|
|
if (IS_ERR(dsi->rst)) {
|
|
err = PTR_ERR(dsi->rst);
|
|
goto remove;
|
|
}
|
|
}
|
|
|
|
dsi->clk = devm_clk_get(&pdev->dev, NULL);
|
|
if (IS_ERR(dsi->clk)) {
|
|
err = dev_err_probe(&pdev->dev, PTR_ERR(dsi->clk),
|
|
"cannot get DSI clock\n");
|
|
goto remove;
|
|
}
|
|
|
|
dsi->clk_lp = devm_clk_get(&pdev->dev, "lp");
|
|
if (IS_ERR(dsi->clk_lp)) {
|
|
err = dev_err_probe(&pdev->dev, PTR_ERR(dsi->clk_lp),
|
|
"cannot get low-power clock\n");
|
|
goto remove;
|
|
}
|
|
|
|
dsi->clk_parent = devm_clk_get(&pdev->dev, "parent");
|
|
if (IS_ERR(dsi->clk_parent)) {
|
|
err = dev_err_probe(&pdev->dev, PTR_ERR(dsi->clk_parent),
|
|
"cannot get parent clock\n");
|
|
goto remove;
|
|
}
|
|
|
|
dsi->vdd = devm_regulator_get(&pdev->dev, "avdd-dsi-csi");
|
|
if (IS_ERR(dsi->vdd)) {
|
|
err = dev_err_probe(&pdev->dev, PTR_ERR(dsi->vdd),
|
|
"cannot get VDD supply\n");
|
|
goto remove;
|
|
}
|
|
|
|
err = tegra_dsi_setup_clocks(dsi);
|
|
if (err < 0) {
|
|
dev_err(&pdev->dev, "cannot setup clocks\n");
|
|
goto remove;
|
|
}
|
|
|
|
dsi->regs = devm_platform_ioremap_resource(pdev, 0);
|
|
if (IS_ERR(dsi->regs)) {
|
|
err = PTR_ERR(dsi->regs);
|
|
goto remove;
|
|
}
|
|
|
|
dsi->mipi = tegra_mipi_request(&pdev->dev, pdev->dev.of_node);
|
|
if (IS_ERR(dsi->mipi)) {
|
|
err = PTR_ERR(dsi->mipi);
|
|
goto remove;
|
|
}
|
|
|
|
dsi->host.ops = &tegra_dsi_host_ops;
|
|
dsi->host.dev = &pdev->dev;
|
|
|
|
err = mipi_dsi_host_register(&dsi->host);
|
|
if (err < 0) {
|
|
dev_err(&pdev->dev, "failed to register DSI host: %d\n", err);
|
|
goto mipi_free;
|
|
}
|
|
|
|
platform_set_drvdata(pdev, dsi);
|
|
pm_runtime_enable(&pdev->dev);
|
|
|
|
INIT_LIST_HEAD(&dsi->client.list);
|
|
dsi->client.ops = &dsi_client_ops;
|
|
dsi->client.dev = &pdev->dev;
|
|
|
|
err = host1x_client_register(&dsi->client);
|
|
if (err < 0) {
|
|
dev_err(&pdev->dev, "failed to register host1x client: %d\n",
|
|
err);
|
|
goto unregister;
|
|
}
|
|
|
|
return 0;
|
|
|
|
unregister:
|
|
pm_runtime_disable(&pdev->dev);
|
|
mipi_dsi_host_unregister(&dsi->host);
|
|
mipi_free:
|
|
tegra_mipi_free(dsi->mipi);
|
|
remove:
|
|
tegra_output_remove(&dsi->output);
|
|
return err;
|
|
}
|
|
|
|
static void tegra_dsi_remove(struct platform_device *pdev)
|
|
{
|
|
struct tegra_dsi *dsi = platform_get_drvdata(pdev);
|
|
|
|
pm_runtime_disable(&pdev->dev);
|
|
|
|
host1x_client_unregister(&dsi->client);
|
|
|
|
tegra_output_remove(&dsi->output);
|
|
|
|
mipi_dsi_host_unregister(&dsi->host);
|
|
tegra_mipi_free(dsi->mipi);
|
|
}
|
|
|
|
static const struct of_device_id tegra_dsi_of_match[] = {
|
|
{ .compatible = "nvidia,tegra210-dsi", },
|
|
{ .compatible = "nvidia,tegra132-dsi", },
|
|
{ .compatible = "nvidia,tegra124-dsi", },
|
|
{ .compatible = "nvidia,tegra114-dsi", },
|
|
{ },
|
|
};
|
|
MODULE_DEVICE_TABLE(of, tegra_dsi_of_match);
|
|
|
|
struct platform_driver tegra_dsi_driver = {
|
|
.driver = {
|
|
.name = "tegra-dsi",
|
|
.of_match_table = tegra_dsi_of_match,
|
|
},
|
|
.probe = tegra_dsi_probe,
|
|
.remove = tegra_dsi_remove,
|
|
};
|