diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2017-11-16 07:42:10 +0300 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2017-11-16 07:42:10 +0300 |
commit | e60e1ee60630cafef5e430c2ae364877e061d980 (patch) | |
tree | 816aeef8fe8d4a2c6a1ebbc7a350839bac8dd4c2 /drivers/gpu/drm/sun4i | |
parent | 5d352e69c60e54b5f04d6e337a1d2bf0dbf3d94a (diff) | |
parent | f150891fd9878ef0d9197c4e8451ce67c3bdd014 (diff) | |
download | linux-e60e1ee60630cafef5e430c2ae364877e061d980.tar.xz |
Merge tag 'drm-for-v4.15' of git://people.freedesktop.org/~airlied/linux
Pull drm updates from Dave Airlie:
"This is the main drm pull request for v4.15.
Core:
- Atomic object lifetime fixes
- Atomic iterator improvements
- Sparse/smatch fixes
- Legacy kms ioctls to be interruptible
- EDID override improvements
- fb/gem helper cleanups
- Simple outreachy patches
- Documentation improvements
- Fix dma-buf rcu races
- DRM mode object leasing for improving VR use cases.
- vgaarb improvements for non-x86 platforms.
New driver:
- tve200: Faraday Technology TVE200 block.
This "TV Encoder" encodes a ITU-T BT.656 stream and can be found in
the StorLink SL3516 (later Cortina Systems CS3516) as well as the
Grain Media GM8180.
New bridges:
- SiI9234 support
New panels:
- S6E63J0X03, OTM8009A, Seiko 43WVF1G, 7" rpi touch panel, Toshiba
LT089AC19000, Innolux AT043TN24
i915:
- Remove Coffeelake from alpha support
- Cannonlake workarounds
- Infoframe refactoring for DisplayPort
- VBT updates
- DisplayPort vswing/emph/buffer translation refactoring
- CCS fixes
- Restore GPU clock boost on missed vblanks
- Scatter list updates for userptr allocations
- Gen9+ transition watermarks
- Display IPC (Isochronous Priority Control)
- Private PAT management
- GVT: improved error handling and pci config sanitizing
- Execlist refactoring
- Transparent Huge Page support
- User defined priorities support
- HuC/GuC firmware refactoring
- DP MST fixes
- eDP power sequencing fixes
- Use RCU instead of stop_machine
- PSR state tracking support
- Eviction fixes
- BDW DP aux channel timeout fixes
- LSPCON fixes
- Cannonlake PLL fixes
amdgpu:
- Per VM BO support
- Powerplay cleanups
- CI powerplay support
- PASID mgr for kfd
- SR-IOV fixes
- initial GPU reset for vega10
- Prime mmap support
- TTM updates
- Clock query interface for Raven
- Fence to handle ioctl
- UVD encode ring support on Polaris
- Transparent huge page DMA support
- Compute LRU pipe tweaks
- BO flag to allow buffers to opt out of implicit sync
- CTX priority setting API
- VRAM lost infrastructure plumbing
qxl:
- fix flicker since atomic rework
amdkfd:
- Further improvements from internal AMD tree
- Usermode events
- Drop radeon support
nouveau:
- Pascal temperature sensor support
- Improved BAR2 handling
- MMU rework to support Pascal MMU
exynos:
- Improved HDMI/mixer support
- HDMI audio interface support
tegra:
- Prep work for tegra186
- Cleanup/fixes
msm:
- Preemption support for a5xx
- Display fixes for 8x96 (snapdragon 820)
- Async cursor plane fixes
- FW loading rework
- GPU debugging improvements
vc4:
- Prep for DSI panels
- fix T-format tiling scanout
- New madvise ioctl
Rockchip:
- LVDS support
omapdrm:
- omap4 HDMI CEC support
etnaviv:
- GPU performance counters groundwork
sun4i:
- refactor driver load + TCON backend
- HDMI improvements
- A31 support
- Misc fixes
udl:
- Probe/EDID read fixes.
tilcdc:
- Misc fixes.
pl111:
- Support more variants
adv7511:
- Improve EDID handling.
- HDMI CEC support
sii8620:
- Add remote control support"
* tag 'drm-for-v4.15' of git://people.freedesktop.org/~airlied/linux: (1480 commits)
drm/rockchip: analogix_dp: Use mutex rather than spinlock
drm/mode_object: fix documentation for object lookups.
drm/i915: Reorder context-close to avoid calling i915_vma_close() under RCU
drm/i915: Move init_clock_gating() back to where it was
drm/i915: Prune the reservation shared fence array
drm/i915: Idle the GPU before shinking everything
drm/i915: Lock llist_del_first() vs llist_del_all()
drm/i915: Calculate ironlake intermediate watermarks correctly, v2.
drm/i915: Disable lazy PPGTT page table optimization for vGPU
drm/i915/execlists: Remove the priority "optimisation"
drm/i915: Filter out spurious execlists context-switch interrupts
drm/amdgpu: use irq-safe lock for kiq->ring_lock
drm/amdgpu: bypass lru touch for KIQ ring submission
drm/amdgpu: Potential uninitialized variable in amdgpu_vm_update_directories()
drm/amdgpu: potential uninitialized variable in amdgpu_vce_ring_parse_cs()
drm/amd/powerplay: initialize a variable before using it
drm/amd/powerplay: suppress KASAN out of bounds warning in vega10_populate_all_memory_levels
drm/amd/amdgpu: fix evicted VRAM bo adjudgement condition
drm/vblank: Tune drm_crtc_accurate_vblank_count() WARN down to a debug
drm/rockchip: add CONFIG_OF dependency for lvds
...
Diffstat (limited to 'drivers/gpu/drm/sun4i')
-rw-r--r-- | drivers/gpu/drm/sun4i/Makefile | 33 | ||||
-rw-r--r-- | drivers/gpu/drm/sun4i/sun4i_backend.c | 113 | ||||
-rw-r--r-- | drivers/gpu/drm/sun4i/sun4i_backend.h | 3 | ||||
-rw-r--r-- | drivers/gpu/drm/sun4i/sun4i_crtc.c | 32 | ||||
-rw-r--r-- | drivers/gpu/drm/sun4i/sun4i_drv.c | 76 | ||||
-rw-r--r-- | drivers/gpu/drm/sun4i/sun4i_framebuffer.c | 3 | ||||
-rw-r--r-- | drivers/gpu/drm/sun4i/sun4i_hdmi.h | 112 | ||||
-rw-r--r-- | drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c | 39 | ||||
-rw-r--r-- | drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c | 277 | ||||
-rw-r--r-- | drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.c | 227 | ||||
-rw-r--r-- | drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c | 69 | ||||
-rw-r--r-- | drivers/gpu/drm/sun4i/sun4i_rgb.c | 29 | ||||
-rw-r--r-- | drivers/gpu/drm/sun4i/sun4i_tcon.c | 466 | ||||
-rw-r--r-- | drivers/gpu/drm/sun4i/sun4i_tcon.h | 30 | ||||
-rw-r--r-- | drivers/gpu/drm/sun4i/sun4i_tv.c | 12 |
15 files changed, 1155 insertions, 366 deletions
diff --git a/drivers/gpu/drm/sun4i/Makefile b/drivers/gpu/drm/sun4i/Makefile index 55b32368f8fb..0c2f8c7facae 100644 --- a/drivers/gpu/drm/sun4i/Makefile +++ b/drivers/gpu/drm/sun4i/Makefile @@ -1,25 +1,26 @@ # SPDX-License-Identifier: GPL-2.0 -sun4i-drm-y += sun4i_drv.o -sun4i-drm-y += sun4i_framebuffer.o +sun4i-backend-y += sun4i_backend.o sun4i_layer.o -sun4i-drm-hdmi-y += sun4i_hdmi_enc.o -sun4i-drm-hdmi-y += sun4i_hdmi_i2c.o -sun4i-drm-hdmi-y += sun4i_hdmi_ddc_clk.o -sun4i-drm-hdmi-y += sun4i_hdmi_tmds_clk.o +sun4i-drm-y += sun4i_drv.o +sun4i-drm-y += sun4i_framebuffer.o -sun4i-tcon-y += sun4i_tcon.o -sun4i-tcon-y += sun4i_rgb.o -sun4i-tcon-y += sun4i_dotclock.o -sun4i-tcon-y += sun4i_crtc.o +sun4i-drm-hdmi-y += sun4i_hdmi_ddc_clk.o +sun4i-drm-hdmi-y += sun4i_hdmi_enc.o +sun4i-drm-hdmi-y += sun4i_hdmi_i2c.o +sun4i-drm-hdmi-y += sun4i_hdmi_tmds_clk.o -sun4i-backend-y += sun4i_backend.o sun4i_layer.o +sun8i-mixer-y += sun8i_mixer.o sun8i_layer.o -sun8i-mixer-y += sun8i_mixer.o sun8i_layer.o +sun4i-tcon-y += sun4i_crtc.o +sun4i-tcon-y += sun4i_dotclock.o +sun4i-tcon-y += sun4i_tcon.o +sun4i-tcon-y += sun4i_rgb.o -obj-$(CONFIG_DRM_SUN4I) += sun4i-drm.o sun4i-tcon.o -obj-$(CONFIG_DRM_SUN4I) += sun6i_drc.o +obj-$(CONFIG_DRM_SUN4I) += sun4i-drm.o +obj-$(CONFIG_DRM_SUN4I) += sun4i-tcon.o obj-$(CONFIG_DRM_SUN4I) += sun4i_tv.o +obj-$(CONFIG_DRM_SUN4I) += sun6i_drc.o -obj-$(CONFIG_DRM_SUN4I_BACKEND) += sun4i-backend.o +obj-$(CONFIG_DRM_SUN4I_BACKEND) += sun4i-backend.o obj-$(CONFIG_DRM_SUN4I_HDMI) += sun4i-drm-hdmi.o -obj-$(CONFIG_DRM_SUN8I_MIXER) += sun8i-mixer.o +obj-$(CONFIG_DRM_SUN8I_MIXER) += sun8i-mixer.o diff --git a/drivers/gpu/drm/sun4i/sun4i_backend.c b/drivers/gpu/drm/sun4i/sun4i_backend.c index ec5943627aa5..847eecbe4d14 100644 --- a/drivers/gpu/drm/sun4i/sun4i_backend.c +++ b/drivers/gpu/drm/sun4i/sun4i_backend.c @@ -20,6 +20,7 @@ #include <linux/component.h> #include <linux/list.h> +#include <linux/of_device.h> #include <linux/of_graph.h> #include <linux/reset.h> @@ -28,6 +29,11 @@ #include "sun4i_layer.h" #include "sunxi_engine.h" +struct sun4i_backend_quirks { + /* backend <-> TCON muxing selection done in backend */ + bool needs_output_muxing; +}; + static const u32 sunxi_rgb2yuv_coef[12] = { 0x00000107, 0x00000204, 0x00000064, 0x00000108, 0x00003f69, 0x00003ed6, 0x000001c1, 0x00000808, @@ -209,24 +215,20 @@ int sun4i_backend_update_layer_buffer(struct sun4i_backend *backend, { struct drm_plane_state *state = plane->state; struct drm_framebuffer *fb = state->fb; - struct drm_gem_cma_object *gem; u32 lo_paddr, hi_paddr; dma_addr_t paddr; - int bpp; - - /* Get the physical address of the buffer in memory */ - gem = drm_fb_cma_get_gem_obj(fb, 0); - - DRM_DEBUG_DRIVER("Using GEM @ %pad\n", &gem->paddr); - - /* Compute the start of the displayed memory */ - bpp = fb->format->cpp[0]; - paddr = gem->paddr + fb->offsets[0]; - paddr += (state->src_x >> 16) * bpp; - paddr += (state->src_y >> 16) * fb->pitches[0]; + /* Get the start of the displayed memory */ + paddr = drm_fb_cma_get_gem_addr(fb, state, 0); DRM_DEBUG_DRIVER("Setting buffer address to %pad\n", &paddr); + /* + * backend DMA accesses DRAM directly, bypassing the system + * bus. As such, the address range is different and the buffer + * address needs to be corrected. + */ + paddr -= PHYS_OFFSET; + /* Write the 32 lower bits of the address (in bits) */ lo_paddr = paddr << 3; DRM_DEBUG_DRIVER("Setting address lower bits to 0x%x\n", lo_paddr); @@ -349,6 +351,7 @@ static int sun4i_backend_bind(struct device *dev, struct device *master, struct drm_device *drm = data; struct sun4i_drv *drv = drm->dev_private; struct sun4i_backend *backend; + const struct sun4i_backend_quirks *quirks; struct resource *res; void __iomem *regs; int i, ret; @@ -369,13 +372,6 @@ static int sun4i_backend_bind(struct device *dev, struct device *master, if (IS_ERR(regs)) return PTR_ERR(regs); - backend->engine.regs = devm_regmap_init_mmio(dev, regs, - &sun4i_backend_regmap_config); - if (IS_ERR(backend->engine.regs)) { - dev_err(dev, "Couldn't create the backend regmap\n"); - return PTR_ERR(backend->engine.regs); - } - backend->reset = devm_reset_control_get(dev, NULL); if (IS_ERR(backend->reset)) { dev_err(dev, "Couldn't get our reset line\n"); @@ -421,9 +417,23 @@ static int sun4i_backend_bind(struct device *dev, struct device *master, } } + backend->engine.regs = devm_regmap_init_mmio(dev, regs, + &sun4i_backend_regmap_config); + if (IS_ERR(backend->engine.regs)) { + dev_err(dev, "Couldn't create the backend regmap\n"); + return PTR_ERR(backend->engine.regs); + } + list_add_tail(&backend->engine.list, &drv->engine_list); - /* Reset the registers */ + /* + * Many of the backend's layer configuration registers have + * undefined default values. This poses a risk as we use + * regmap_update_bits in some places, and don't overwrite + * the whole register. + * + * Clear the registers here to have something predictable. + */ for (i = 0x800; i < 0x1000; i += 4) regmap_write(backend->engine.regs, i, 0); @@ -436,6 +446,27 @@ static int sun4i_backend_bind(struct device *dev, struct device *master, SUN4I_BACKEND_MODCTL_DEBE_EN | SUN4I_BACKEND_MODCTL_START_CTL); + /* Set output selection if needed */ + quirks = of_device_get_match_data(dev); + if (quirks->needs_output_muxing) { + /* + * We assume there is no dynamic muxing of backends + * and TCONs, so we select the backend with same ID. + * + * While dynamic selection might be interesting, since + * the CRTC is tied to the TCON, while the layers are + * tied to the backends, this means, we will need to + * switch between groups of layers. There might not be + * a way to represent this constraint in DRM. + */ + regmap_update_bits(backend->engine.regs, + SUN4I_BACKEND_MODCTL_REG, + SUN4I_BACKEND_MODCTL_OUT_SEL, + (backend->engine.id + ? SUN4I_BACKEND_MODCTL_OUT_LCD1 + : SUN4I_BACKEND_MODCTL_OUT_LCD0)); + } + return 0; err_disable_ram_clk: @@ -483,10 +514,44 @@ static int sun4i_backend_remove(struct platform_device *pdev) return 0; } +static const struct sun4i_backend_quirks sun4i_backend_quirks = { + .needs_output_muxing = true, +}; + +static const struct sun4i_backend_quirks sun5i_backend_quirks = { +}; + +static const struct sun4i_backend_quirks sun6i_backend_quirks = { +}; + +static const struct sun4i_backend_quirks sun7i_backend_quirks = { + .needs_output_muxing = true, +}; + +static const struct sun4i_backend_quirks sun8i_a33_backend_quirks = { +}; + static const struct of_device_id sun4i_backend_of_table[] = { - { .compatible = "allwinner,sun5i-a13-display-backend" }, - { .compatible = "allwinner,sun6i-a31-display-backend" }, - { .compatible = "allwinner,sun8i-a33-display-backend" }, + { + .compatible = "allwinner,sun4i-a10-display-backend", + .data = &sun4i_backend_quirks, + }, + { + .compatible = "allwinner,sun5i-a13-display-backend", + .data = &sun5i_backend_quirks, + }, + { + .compatible = "allwinner,sun6i-a31-display-backend", + .data = &sun6i_backend_quirks, + }, + { + .compatible = "allwinner,sun7i-a20-display-backend", + .data = &sun7i_backend_quirks, + }, + { + .compatible = "allwinner,sun8i-a33-display-backend", + .data = &sun8i_a33_backend_quirks, + }, { } }; MODULE_DEVICE_TABLE(of, sun4i_backend_of_table); diff --git a/drivers/gpu/drm/sun4i/sun4i_backend.h b/drivers/gpu/drm/sun4i/sun4i_backend.h index 21945af67a9d..ac3cc029f5cd 100644 --- a/drivers/gpu/drm/sun4i/sun4i_backend.h +++ b/drivers/gpu/drm/sun4i/sun4i_backend.h @@ -25,7 +25,8 @@ #define SUN4I_BACKEND_MODCTL_LINE_SEL BIT(29) #define SUN4I_BACKEND_MODCTL_ITLMOD_EN BIT(28) #define SUN4I_BACKEND_MODCTL_OUT_SEL GENMASK(22, 20) -#define SUN4I_BACKEND_MODCTL_OUT_LCD (0 << 20) +#define SUN4I_BACKEND_MODCTL_OUT_LCD0 (0 << 20) +#define SUN4I_BACKEND_MODCTL_OUT_LCD1 (1 << 20) #define SUN4I_BACKEND_MODCTL_OUT_FE0 (6 << 20) #define SUN4I_BACKEND_MODCTL_OUT_FE1 (7 << 20) #define SUN4I_BACKEND_MODCTL_HWC_EN BIT(16) diff --git a/drivers/gpu/drm/sun4i/sun4i_crtc.c b/drivers/gpu/drm/sun4i/sun4i_crtc.c index d097c6f93ad0..5decae0069d0 100644 --- a/drivers/gpu/drm/sun4i/sun4i_crtc.c +++ b/drivers/gpu/drm/sun4i/sun4i_crtc.c @@ -30,6 +30,22 @@ #include "sunxi_engine.h" #include "sun4i_tcon.h" +/* + * While this isn't really working in the DRM theory, in practice we + * can only ever have one encoder per TCON since we have a mux in our + * TCON. + */ +static struct drm_encoder *sun4i_crtc_get_encoder(struct drm_crtc *crtc) +{ + struct drm_encoder *encoder; + + drm_for_each_encoder(encoder, crtc->dev) + if (encoder->crtc == crtc) + return encoder; + + return NULL; +} + static void sun4i_crtc_atomic_begin(struct drm_crtc *crtc, struct drm_crtc_state *old_state) { @@ -72,11 +88,12 @@ static void sun4i_crtc_atomic_flush(struct drm_crtc *crtc, static void sun4i_crtc_atomic_disable(struct drm_crtc *crtc, struct drm_crtc_state *old_state) { + struct drm_encoder *encoder = sun4i_crtc_get_encoder(crtc); struct sun4i_crtc *scrtc = drm_crtc_to_sun4i_crtc(crtc); DRM_DEBUG_DRIVER("Disabling the CRTC\n"); - sun4i_tcon_disable(scrtc->tcon); + sun4i_tcon_set_status(scrtc->tcon, encoder, false); if (crtc->state->event && !crtc->state->active) { spin_lock_irq(&crtc->dev->event_lock); @@ -90,11 +107,21 @@ static void sun4i_crtc_atomic_disable(struct drm_crtc *crtc, static void sun4i_crtc_atomic_enable(struct drm_crtc *crtc, struct drm_crtc_state *old_state) { + struct drm_encoder *encoder = sun4i_crtc_get_encoder(crtc); struct sun4i_crtc *scrtc = drm_crtc_to_sun4i_crtc(crtc); DRM_DEBUG_DRIVER("Enabling the CRTC\n"); - sun4i_tcon_enable(scrtc->tcon); + sun4i_tcon_set_status(scrtc->tcon, encoder, true); +} + +static void sun4i_crtc_mode_set_nofb(struct drm_crtc *crtc) +{ + struct drm_display_mode *mode = &crtc->state->adjusted_mode; + struct drm_encoder *encoder = sun4i_crtc_get_encoder(crtc); + struct sun4i_crtc *scrtc = drm_crtc_to_sun4i_crtc(crtc); + + sun4i_tcon_mode_set(scrtc->tcon, encoder, mode); } static const struct drm_crtc_helper_funcs sun4i_crtc_helper_funcs = { @@ -102,6 +129,7 @@ static const struct drm_crtc_helper_funcs sun4i_crtc_helper_funcs = { .atomic_flush = sun4i_crtc_atomic_flush, .atomic_enable = sun4i_crtc_atomic_enable, .atomic_disable = sun4i_crtc_atomic_disable, + .mode_set_nofb = sun4i_crtc_mode_set_nofb, }; static int sun4i_crtc_enable_vblank(struct drm_crtc *crtc) diff --git a/drivers/gpu/drm/sun4i/sun4i_drv.c b/drivers/gpu/drm/sun4i/sun4i_drv.c index ace59651892f..75c76cdd82bc 100644 --- a/drivers/gpu/drm/sun4i/sun4i_drv.c +++ b/drivers/gpu/drm/sun4i/sun4i_drv.c @@ -11,6 +11,7 @@ */ #include <linux/component.h> +#include <linux/kfifo.h> #include <linux/of_graph.h> #include <linux/of_reserved_mem.h> @@ -106,11 +107,6 @@ static int sun4i_drv_bind(struct device *dev) goto free_drm; } - /* drm_vblank_init calls kcalloc, which can fail */ - ret = drm_vblank_init(drm, 1); - if (ret) - goto free_mem_region; - drm_mode_config_init(drm); ret = component_bind_all(drm->dev, drm); @@ -119,6 +115,11 @@ static int sun4i_drv_bind(struct device *dev) goto cleanup_mode_config; } + /* drm_vblank_init calls kcalloc, which can fail */ + ret = drm_vblank_init(drm, drm->mode_config.num_crtc); + if (ret) + goto free_mem_region; + drm->irq_enabled = true; /* Remove early framebuffers (ie. simplefb) */ @@ -177,16 +178,20 @@ static bool sun4i_drv_node_is_connector(struct device_node *node) static bool sun4i_drv_node_is_frontend(struct device_node *node) { - return of_device_is_compatible(node, "allwinner,sun5i-a13-display-frontend") || + return of_device_is_compatible(node, "allwinner,sun4i-a10-display-frontend") || + of_device_is_compatible(node, "allwinner,sun5i-a13-display-frontend") || of_device_is_compatible(node, "allwinner,sun6i-a31-display-frontend") || + of_device_is_compatible(node, "allwinner,sun7i-a20-display-frontend") || of_device_is_compatible(node, "allwinner,sun8i-a33-display-frontend"); } static bool sun4i_drv_node_is_tcon(struct device_node *node) { - return of_device_is_compatible(node, "allwinner,sun5i-a13-tcon") || + return of_device_is_compatible(node, "allwinner,sun4i-a10-tcon") || + of_device_is_compatible(node, "allwinner,sun5i-a13-tcon") || of_device_is_compatible(node, "allwinner,sun6i-a31-tcon") || of_device_is_compatible(node, "allwinner,sun6i-a31s-tcon") || + of_device_is_compatible(node, "allwinner,sun7i-a20-tcon") || of_device_is_compatible(node, "allwinner,sun8i-a33-tcon") || of_device_is_compatible(node, "allwinner,sun8i-v3s-tcon"); } @@ -200,7 +205,33 @@ static int compare_of(struct device *dev, void *data) return dev->of_node == data; } +/* + * The encoder drivers use drm_of_find_possible_crtcs to get upstream + * crtcs from the device tree using of_graph. For the results to be + * correct, encoders must be probed/bound after _all_ crtcs have been + * created. The existing code uses a depth first recursive traversal + * of the of_graph, which means the encoders downstream of the TCON + * get add right after the first TCON. The second TCON or CRTC will + * never be properly associated with encoders connected to it. + * + * Also, in a dual display pipeline setup, both frontends can feed + * either backend, and both backends can feed either TCON, we want + * all components of the same type to be added before the next type + * in the pipeline. Fortunately, the pipelines are perfectly symmetric, + * i.e. components of the same type are at the same depth when counted + * from the frontend. The only exception is the third pipeline in + * the A80 SoC, which we do not support anyway. + * + * Hence we can use a breadth first search traversal order to add + * components. We do not need to check for duplicates. The component + * matching system handles this for us. + */ +struct endpoint_list { + DECLARE_KFIFO(fifo, struct device_node *, 16); +}; + static int sun4i_drv_add_endpoints(struct device *dev, + struct endpoint_list *list, struct component_match **match, struct device_node *node) { @@ -264,10 +295,7 @@ static int sun4i_drv_add_endpoints(struct device *dev, } } - /* Walk down our tree */ - count += sun4i_drv_add_endpoints(dev, match, remote); - - of_node_put(remote); + kfifo_put(&list->fifo, remote); } return count; @@ -276,8 +304,11 @@ static int sun4i_drv_add_endpoints(struct device *dev, static int sun4i_drv_probe(struct platform_device *pdev) { struct component_match *match = NULL; - struct device_node *np = pdev->dev.of_node; - int i, count = 0; + struct device_node *np = pdev->dev.of_node, *endpoint; + struct endpoint_list list; + int i, ret, count = 0; + + INIT_KFIFO(list.fifo); for (i = 0;; i++) { struct device_node *pipeline = of_parse_phandle(np, @@ -286,12 +317,19 @@ static int sun4i_drv_probe(struct platform_device *pdev) if (!pipeline) break; - count += sun4i_drv_add_endpoints(&pdev->dev, &match, - pipeline); - of_node_put(pipeline); + kfifo_put(&list.fifo, pipeline); + } + + while (kfifo_get(&list.fifo, &endpoint)) { + /* process this endpoint */ + ret = sun4i_drv_add_endpoints(&pdev->dev, &list, &match, + endpoint); + + /* sun4i_drv_add_endpoints can fail to allocate memory */ + if (ret < 0) + return ret; - DRM_DEBUG_DRIVER("Queued %d outputs on pipeline %d\n", - count, i); + count += ret; } if (count) @@ -308,10 +346,12 @@ static int sun4i_drv_remove(struct platform_device *pdev) } static const struct of_device_id sun4i_drv_of_table[] = { + { .compatible = "allwinner,sun4i-a10-display-engine" }, { .compatible = "allwinner,sun5i-a10s-display-engine" }, { .compatible = "allwinner,sun5i-a13-display-engine" }, { .compatible = "allwinner,sun6i-a31-display-engine" }, { .compatible = "allwinner,sun6i-a31s-display-engine" }, + { .compatible = "allwinner,sun7i-a20-display-engine" }, { .compatible = "allwinner,sun8i-a33-display-engine" }, { .compatible = "allwinner,sun8i-v3s-display-engine" }, { } diff --git a/drivers/gpu/drm/sun4i/sun4i_framebuffer.c b/drivers/gpu/drm/sun4i/sun4i_framebuffer.c index 9872e0fc03b0..2992f0a6b349 100644 --- a/drivers/gpu/drm/sun4i/sun4i_framebuffer.c +++ b/drivers/gpu/drm/sun4i/sun4i_framebuffer.c @@ -12,6 +12,7 @@ #include <drm/drm_atomic_helper.h> #include <drm/drm_fb_cma_helper.h> +#include <drm/drm_gem_framebuffer_helper.h> #include <drm/drmP.h> #include "sun4i_drv.h" @@ -28,7 +29,7 @@ static const struct drm_mode_config_funcs sun4i_de_mode_config_funcs = { .output_poll_changed = sun4i_de_output_poll_changed, .atomic_check = drm_atomic_helper_check, .atomic_commit = drm_atomic_helper_commit, - .fb_create = drm_fb_cma_create, + .fb_create = drm_gem_fb_create, }; struct drm_fbdev_cma *sun4i_framebuffer_init(struct drm_device *drm) diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi.h b/drivers/gpu/drm/sun4i/sun4i_hdmi.h index a1f8cba251a2..b685ee11623d 100644 --- a/drivers/gpu/drm/sun4i/sun4i_hdmi.h +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi.h @@ -14,6 +14,7 @@ #include <drm/drm_connector.h> #include <drm/drm_encoder.h> +#include <linux/regmap.h> #include <media/cec-pin.h> @@ -58,16 +59,24 @@ #define SUN4I_HDMI_PAD_CTRL0_TXEN BIT(23) #define SUN4I_HDMI_PAD_CTRL1_REG 0x204 +#define SUN4I_HDMI_PAD_CTRL1_UNKNOWN BIT(24) /* set on A31 */ #define SUN4I_HDMI_PAD_CTRL1_AMP_OPT BIT(23) #define SUN4I_HDMI_PAD_CTRL1_AMPCK_OPT BIT(22) #define SUN4I_HDMI_PAD_CTRL1_EMP_OPT BIT(20) #define SUN4I_HDMI_PAD_CTRL1_EMPCK_OPT BIT(19) +#define SUN4I_HDMI_PAD_CTRL1_PWSCK BIT(18) +#define SUN4I_HDMI_PAD_CTRL1_PWSDT BIT(17) #define SUN4I_HDMI_PAD_CTRL1_REG_DEN BIT(15) #define SUN4I_HDMI_PAD_CTRL1_REG_DENCK BIT(14) #define SUN4I_HDMI_PAD_CTRL1_REG_EMP(n) (((n) & 7) << 10) #define SUN4I_HDMI_PAD_CTRL1_HALVE_CLK BIT(6) #define SUN4I_HDMI_PAD_CTRL1_REG_AMP(n) (((n) & 7) << 3) +/* These bits seem to invert the TMDS data channels */ +#define SUN4I_HDMI_PAD_CTRL1_INVERT_R BIT(2) +#define SUN4I_HDMI_PAD_CTRL1_INVERT_G BIT(1) +#define SUN4I_HDMI_PAD_CTRL1_INVERT_B BIT(0) + #define SUN4I_HDMI_PLL_CTRL_REG 0x208 #define SUN4I_HDMI_PLL_CTRL_PLL_EN BIT(31) #define SUN4I_HDMI_PLL_CTRL_BWS BIT(30) @@ -152,21 +161,106 @@ #define SUN4I_HDMI_DDC_FIFO_SIZE 16 +/* A31 specific */ +#define SUN6I_HDMI_DDC_CTRL_REG 0x500 +#define SUN6I_HDMI_DDC_CTRL_RESET BIT(31) +#define SUN6I_HDMI_DDC_CTRL_START_CMD BIT(27) +#define SUN6I_HDMI_DDC_CTRL_SDA_ENABLE BIT(6) +#define SUN6I_HDMI_DDC_CTRL_SCL_ENABLE BIT(4) +#define SUN6I_HDMI_DDC_CTRL_ENABLE BIT(0) + +#define SUN6I_HDMI_DDC_CMD_REG 0x508 +#define SUN6I_HDMI_DDC_CMD_BYTE_COUNT(count) ((count) << 16) +/* command types in lower 3 bits are the same as sun4i */ + +#define SUN6I_HDMI_DDC_ADDR_REG 0x50c +#define SUN6I_HDMI_DDC_ADDR_SEGMENT(seg) (((seg) & 0xff) << 24) +#define SUN6I_HDMI_DDC_ADDR_EDDC(addr) (((addr) & 0xff) << 16) +#define SUN6I_HDMI_DDC_ADDR_OFFSET(off) (((off) & 0xff) << 8) +#define SUN6I_HDMI_DDC_ADDR_SLAVE(addr) (((addr) & 0xff) << 1) + +#define SUN6I_HDMI_DDC_INT_STATUS_REG 0x514 +#define SUN6I_HDMI_DDC_INT_STATUS_TIMEOUT BIT(8) +/* lower 8 bits are the same as sun4i */ + +#define SUN6I_HDMI_DDC_FIFO_CTRL_REG 0x518 +#define SUN6I_HDMI_DDC_FIFO_CTRL_CLEAR BIT(15) +/* lower 9 bits are the same as sun4i */ + +#define SUN6I_HDMI_DDC_CLK_REG 0x520 +/* DDC CLK bit fields are the same, but the formula is not */ + +#define SUN6I_HDMI_DDC_FIFO_DATA_REG 0x580 + enum sun4i_hdmi_pkt_type { SUN4I_HDMI_PKT_AVI = 2, SUN4I_HDMI_PKT_END = 15, }; +struct sun4i_hdmi_variant { + bool has_ddc_parent_clk; + bool has_reset_control; + + u32 pad_ctrl0_init_val; + u32 pad_ctrl1_init_val; + u32 pll_ctrl_init_val; + + struct reg_field ddc_clk_reg; + u8 ddc_clk_pre_divider; + u8 ddc_clk_m_offset; + + u8 tmds_clk_div_offset; + + /* Register fields for I2C adapter */ + struct reg_field field_ddc_en; + struct reg_field field_ddc_start; + struct reg_field field_ddc_reset; + struct reg_field field_ddc_addr_reg; + struct reg_field field_ddc_slave_addr; + struct reg_field field_ddc_int_mask; + struct reg_field field_ddc_int_status; + struct reg_field field_ddc_fifo_clear; + struct reg_field field_ddc_fifo_rx_thres; + struct reg_field field_ddc_fifo_tx_thres; + struct reg_field field_ddc_byte_count; + struct reg_field field_ddc_cmd; + struct reg_field field_ddc_sda_en; + struct reg_field field_ddc_sck_en; + + /* DDC FIFO register offset */ + u32 ddc_fifo_reg; + + /* + * DDC FIFO threshold boundary conditions + * + * This is used to cope with the threshold boundary condition + * being slightly different on sun5i and sun6i. + * + * On sun5i the threshold is exclusive, i.e. does not include, + * the value of the threshold. ( > for RX; < for TX ) + * On sun6i the threshold is inclusive, i.e. includes, the + * value of the threshold. ( >= for RX; <= for TX ) + */ + bool ddc_fifo_thres_incl; + + bool ddc_fifo_has_dir; +}; + struct sun4i_hdmi { struct drm_connector connector; struct drm_encoder encoder; struct device *dev; void __iomem *base; + struct regmap *regmap; + + /* Reset control */ + struct reset_control *reset; /* Parent clocks */ struct clk *bus_clk; struct clk *mod_clk; + struct clk *ddc_parent_clk; struct clk *pll0_clk; struct clk *pll1_clk; @@ -176,10 +270,28 @@ struct sun4i_hdmi { struct i2c_adapter *i2c; + /* Regmap fields for I2C adapter */ + struct regmap_field *field_ddc_en; + struct regmap_field *field_ddc_start; + struct regmap_field *field_ddc_reset; + struct regmap_field *field_ddc_addr_reg; + struct regmap_field *field_ddc_slave_addr; + struct regmap_field *field_ddc_int_mask; + struct regmap_field *field_ddc_int_status; + struct regmap_field *field_ddc_fifo_clear; + struct regmap_field *field_ddc_fifo_rx_thres; + struct regmap_field *field_ddc_fifo_tx_thres; + struct regmap_field *field_ddc_byte_count; + struct regmap_field *field_ddc_cmd; + struct regmap_field *field_ddc_sda_en; + struct regmap_field *field_ddc_sck_en; + struct sun4i_drv *drv; bool hdmi_monitor; struct cec_adapter *cec_adap; + + const struct sun4i_hdmi_variant *variant; }; int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *clk); diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c index 4692e8c345ed..e826da34e919 100644 --- a/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c @@ -11,13 +11,16 @@ */ #include <linux/clk-provider.h> +#include <linux/regmap.h> -#include "sun4i_tcon.h" #include "sun4i_hdmi.h" struct sun4i_ddc { struct clk_hw hw; struct sun4i_hdmi *hdmi; + struct regmap_field *reg; + u8 pre_div; + u8 m_offset; }; static inline struct sun4i_ddc *hw_to_ddc(struct clk_hw *hw) @@ -27,6 +30,8 @@ static inline struct sun4i_ddc *hw_to_ddc(struct clk_hw *hw) static unsigned long sun4i_ddc_calc_divider(unsigned long rate, unsigned long parent_rate, + const u8 pre_div, + const u8 m_offset, u8 *m, u8 *n) { unsigned long best_rate = 0; @@ -36,7 +41,8 @@ static unsigned long sun4i_ddc_calc_divider(unsigned long rate, for (_n = 0; _n < 8; _n++) { unsigned long tmp_rate; - tmp_rate = (((parent_rate / 2) / 10) >> _n) / (_m + 1); + tmp_rate = (((parent_rate / pre_div) / 10) >> _n) / + (_m + m_offset); if (tmp_rate > rate) continue; @@ -60,21 +66,25 @@ static unsigned long sun4i_ddc_calc_divider(unsigned long rate, static long sun4i_ddc_round_rate(struct clk_hw *hw, unsigned long rate, unsigned long *prate) { - return sun4i_ddc_calc_divider(rate, *prate, NULL, NULL); + struct sun4i_ddc *ddc = hw_to_ddc(hw); + + return sun4i_ddc_calc_divider(rate, *prate, ddc->pre_div, + ddc->m_offset, NULL, NULL); } static unsigned long sun4i_ddc_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) { struct sun4i_ddc *ddc = hw_to_ddc(hw); - u32 reg; + unsigned int reg; u8 m, n; - reg = readl(ddc->hdmi->base + SUN4I_HDMI_DDC_CLK_REG); - m = (reg >> 3) & 0x7; + regmap_field_read(ddc->reg, ®); + m = (reg >> 3) & 0xf; n = reg & 0x7; - return (((parent_rate / 2) / 10) >> n) / (m + 1); + return (((parent_rate / ddc->pre_div) / 10) >> n) / + (m + ddc->m_offset); } static int sun4i_ddc_set_rate(struct clk_hw *hw, unsigned long rate, @@ -83,10 +93,12 @@ static int sun4i_ddc_set_rate(struct clk_hw *hw, unsigned long rate, struct sun4i_ddc *ddc = hw_to_ddc(hw); u8 div_m, div_n; - sun4i_ddc_calc_divider(rate, parent_rate, &div_m, &div_n); + sun4i_ddc_calc_divider(rate, parent_rate, ddc->pre_div, + ddc->m_offset, &div_m, &div_n); - writel(SUN4I_HDMI_DDC_CLK_M(div_m) | SUN4I_HDMI_DDC_CLK_N(div_n), - ddc->hdmi->base + SUN4I_HDMI_DDC_CLK_REG); + regmap_field_write(ddc->reg, + SUN4I_HDMI_DDC_CLK_M(div_m) | + SUN4I_HDMI_DDC_CLK_N(div_n)); return 0; } @@ -111,6 +123,11 @@ int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *parent) if (!ddc) return -ENOMEM; + ddc->reg = devm_regmap_field_alloc(hdmi->dev, hdmi->regmap, + hdmi->variant->ddc_clk_reg); + if (IS_ERR(ddc->reg)) + return PTR_ERR(ddc->reg); + init.name = "hdmi-ddc"; init.ops = &sun4i_ddc_ops; init.parent_names = &parent_name; @@ -118,6 +135,8 @@ int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *parent) ddc->hdmi = hdmi; ddc->hw.init = &init; + ddc->pre_div = hdmi->variant->ddc_clk_pre_divider; + ddc->m_offset = hdmi->variant->ddc_clk_m_offset; hdmi->ddc_clk = devm_clk_register(hdmi->dev, &ddc->hw); if (IS_ERR(hdmi->ddc_clk)) diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c index 3cf1a6932fac..dda904ec0534 100644 --- a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c @@ -20,14 +20,16 @@ #include <linux/clk.h> #include <linux/component.h> #include <linux/iopoll.h> +#include <linux/of_device.h> #include <linux/platform_device.h> #include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/reset.h> #include "sun4i_backend.h" #include "sun4i_crtc.h" #include "sun4i_drv.h" #include "sun4i_hdmi.h" -#include "sun4i_tcon.h" static inline struct sun4i_hdmi * drm_encoder_to_sun4i_hdmi(struct drm_encoder *encoder) @@ -83,8 +85,6 @@ static int sun4i_hdmi_atomic_check(struct drm_encoder *encoder, static void sun4i_hdmi_disable(struct drm_encoder *encoder) { struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder); - struct sun4i_crtc *crtc = drm_crtc_to_sun4i_crtc(encoder->crtc); - struct sun4i_tcon *tcon = crtc->tcon; u32 val; DRM_DEBUG_DRIVER("Disabling the HDMI Output\n"); @@ -92,22 +92,16 @@ static void sun4i_hdmi_disable(struct drm_encoder *encoder) val = readl(hdmi->base + SUN4I_HDMI_VID_CTRL_REG); val &= ~SUN4I_HDMI_VID_CTRL_ENABLE; writel(val, hdmi->base + SUN4I_HDMI_VID_CTRL_REG); - - sun4i_tcon_channel_disable(tcon, 1); } static void sun4i_hdmi_enable(struct drm_encoder *encoder) { struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode; struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder); - struct sun4i_crtc *crtc = drm_crtc_to_sun4i_crtc(encoder->crtc); - struct sun4i_tcon *tcon = crtc->tcon; u32 val = 0; DRM_DEBUG_DRIVER("Enabling the HDMI Output\n"); - sun4i_tcon_channel_enable(tcon, 1); - sun4i_hdmi_setup_avi_infoframes(hdmi, mode); val |= SUN4I_HDMI_PKT_CTRL_TYPE(0, SUN4I_HDMI_PKT_AVI); val |= SUN4I_HDMI_PKT_CTRL_TYPE(1, SUN4I_HDMI_PKT_END); @@ -125,15 +119,9 @@ static void sun4i_hdmi_mode_set(struct drm_encoder *encoder, struct drm_display_mode *adjusted_mode) { struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder); - struct sun4i_crtc *crtc = drm_crtc_to_sun4i_crtc(encoder->crtc); - struct sun4i_tcon *tcon = crtc->tcon; unsigned int x, y; u32 val; - sun4i_tcon1_mode_set(tcon, mode); - sun4i_tcon_set_mux(tcon, 1, encoder); - - clk_set_rate(tcon->sclk1, mode->crtc_clock * 1000); clk_set_rate(hdmi->mod_clk, mode->crtc_clock * 1000); clk_set_rate(hdmi->tmds_clk, mode->crtc_clock * 1000); @@ -141,6 +129,22 @@ static void sun4i_hdmi_mode_set(struct drm_encoder *encoder, writel(SUN4I_HDMI_UNKNOWN_INPUT_SYNC, hdmi->base + SUN4I_HDMI_UNKNOWN_REG); + /* + * Setup output pad (?) controls + * + * This is done here instead of at probe/bind time because + * the controller seems to toggle some of the bits on its own. + * + * We can't just initialize the register there, we need to + * protect the clock bits that have already been read out and + * cached by the clock framework. + */ + val = readl(hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG); + val &= SUN4I_HDMI_PAD_CTRL1_HALVE_CLK; + val |= hdmi->variant->pad_ctrl1_init_val; + writel(val, hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG); + val = readl(hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG); + /* Setup timing registers */ writel(SUN4I_HDMI_VID_TIMING_X(mode->hdisplay) | SUN4I_HDMI_VID_TIMING_Y(mode->vdisplay), @@ -267,6 +271,176 @@ static const struct cec_pin_ops sun4i_hdmi_cec_pin_ops = { }; #endif +#define SUN4I_HDMI_PAD_CTRL1_MASK (GENMASK(24, 7) | GENMASK(5, 0)) +#define SUN4I_HDMI_PLL_CTRL_MASK (GENMASK(31, 8) | GENMASK(3, 0)) + +/* Only difference from sun5i is AMP is 4 instead of 6 */ +static const struct sun4i_hdmi_variant sun4i_variant = { + .pad_ctrl0_init_val = SUN4I_HDMI_PAD_CTRL0_TXEN | + SUN4I_HDMI_PAD_CTRL0_CKEN | + SUN4I_HDMI_PAD_CTRL0_PWENG | + SUN4I_HDMI_PAD_CTRL0_PWEND | + SUN4I_HDMI_PAD_CTRL0_PWENC | + SUN4I_HDMI_PAD_CTRL0_LDODEN | + SUN4I_HDMI_PAD_CTRL0_LDOCEN | + SUN4I_HDMI_PAD_CTRL0_BIASEN, + .pad_ctrl1_init_val = SUN4I_HDMI_PAD_CTRL1_REG_AMP(4) | + SUN4I_HDMI_PAD_CTRL1_REG_EMP(2) | + SUN4I_HDMI_PAD_CTRL1_REG_DENCK | + SUN4I_HDMI_PAD_CTRL1_REG_DEN | + SUN4I_HDMI_PAD_CTRL1_EMPCK_OPT | + SUN4I_HDMI_PAD_CTRL1_EMP_OPT | + SUN4I_HDMI_PAD_CTRL1_AMPCK_OPT | + SUN4I_HDMI_PAD_CTRL1_AMP_OPT, + .pll_ctrl_init_val = SUN4I_HDMI_PLL_CTRL_VCO_S(8) | + SUN4I_HDMI_PLL_CTRL_CS(7) | + SUN4I_HDMI_PLL_CTRL_CP_S(15) | + SUN4I_HDMI_PLL_CTRL_S(7) | + SUN4I_HDMI_PLL_CTRL_VCO_GAIN(4) | + SUN4I_HDMI_PLL_CTRL_SDIV2 | + SUN4I_HDMI_PLL_CTRL_LDO2_EN | + SUN4I_HDMI_PLL_CTRL_LDO1_EN | + SUN4I_HDMI_PLL_CTRL_HV_IS_33 | + SUN4I_HDMI_PLL_CTRL_BWS | + SUN4I_HDMI_PLL_CTRL_PLL_EN, + + .ddc_clk_reg = REG_FIELD(SUN4I_HDMI_DDC_CLK_REG, 0, 6), + .ddc_clk_pre_divider = 2, + .ddc_clk_m_offset = 1, + + .field_ddc_en = REG_FIELD(SUN4I_HDMI_DDC_CTRL_REG, 31, 31), + .field_ddc_start = REG_FIELD(SUN4I_HDMI_DDC_CTRL_REG, 30, 30), + .field_ddc_reset = REG_FIELD(SUN4I_HDMI_DDC_CTRL_REG, 0, 0), + .field_ddc_addr_reg = REG_FIELD(SUN4I_HDMI_DDC_ADDR_REG, 0, 31), + .field_ddc_slave_addr = REG_FIELD(SUN4I_HDMI_DDC_ADDR_REG, 0, 6), + .field_ddc_int_status = REG_FIELD(SUN4I_HDMI_DDC_INT_STATUS_REG, 0, 8), + .field_ddc_fifo_clear = REG_FIELD(SUN4I_HDMI_DDC_FIFO_CTRL_REG, 31, 31), + .field_ddc_fifo_rx_thres = REG_FIELD(SUN4I_HDMI_DDC_FIFO_CTRL_REG, 4, 7), + .field_ddc_fifo_tx_thres = REG_FIELD(SUN4I_HDMI_DDC_FIFO_CTRL_REG, 0, 3), + .field_ddc_byte_count = REG_FIELD(SUN4I_HDMI_DDC_BYTE_COUNT_REG, 0, 9), + .field_ddc_cmd = REG_FIELD(SUN4I_HDMI_DDC_CMD_REG, 0, 2), + .field_ddc_sda_en = REG_FIELD(SUN4I_HDMI_DDC_LINE_CTRL_REG, 9, 9), + .field_ddc_sck_en = REG_FIELD(SUN4I_HDMI_DDC_LINE_CTRL_REG, 8, 8), + + .ddc_fifo_reg = SUN4I_HDMI_DDC_FIFO_DATA_REG, + .ddc_fifo_has_dir = true, +}; + +static const struct sun4i_hdmi_variant sun5i_variant = { + .pad_ctrl0_init_val = SUN4I_HDMI_PAD_CTRL0_TXEN | + SUN4I_HDMI_PAD_CTRL0_CKEN | + SUN4I_HDMI_PAD_CTRL0_PWENG | + SUN4I_HDMI_PAD_CTRL0_PWEND | + SUN4I_HDMI_PAD_CTRL0_PWENC | + SUN4I_HDMI_PAD_CTRL0_LDODEN | + SUN4I_HDMI_PAD_CTRL0_LDOCEN | + SUN4I_HDMI_PAD_CTRL0_BIASEN, + .pad_ctrl1_init_val = SUN4I_HDMI_PAD_CTRL1_REG_AMP(6) | + SUN4I_HDMI_PAD_CTRL1_REG_EMP(2) | + SUN4I_HDMI_PAD_CTRL1_REG_DENCK | + SUN4I_HDMI_PAD_CTRL1_REG_DEN | + SUN4I_HDMI_PAD_CTRL1_EMPCK_OPT | + SUN4I_HDMI_PAD_CTRL1_EMP_OPT | + SUN4I_HDMI_PAD_CTRL1_AMPCK_OPT | + SUN4I_HDMI_PAD_CTRL1_AMP_OPT, + .pll_ctrl_init_val = SUN4I_HDMI_PLL_CTRL_VCO_S(8) | + SUN4I_HDMI_PLL_CTRL_CS(7) | + SUN4I_HDMI_PLL_CTRL_CP_S(15) | + SUN4I_HDMI_PLL_CTRL_S(7) | + SUN4I_HDMI_PLL_CTRL_VCO_GAIN(4) | + SUN4I_HDMI_PLL_CTRL_SDIV2 | + SUN4I_HDMI_PLL_CTRL_LDO2_EN | + SUN4I_HDMI_PLL_CTRL_LDO1_EN | + SUN4I_HDMI_PLL_CTRL_HV_IS_33 | + SUN4I_HDMI_PLL_CTRL_BWS | + SUN4I_HDMI_PLL_CTRL_PLL_EN, + + .ddc_clk_reg = REG_FIELD(SUN4I_HDMI_DDC_CLK_REG, 0, 6), + .ddc_clk_pre_divider = 2, + .ddc_clk_m_offset = 1, + + .field_ddc_en = REG_FIELD(SUN4I_HDMI_DDC_CTRL_REG, 31, 31), + .field_ddc_start = REG_FIELD(SUN4I_HDMI_DDC_CTRL_REG, 30, 30), + .field_ddc_reset = REG_FIELD(SUN4I_HDMI_DDC_CTRL_REG, 0, 0), + .field_ddc_addr_reg = REG_FIELD(SUN4I_HDMI_DDC_ADDR_REG, 0, 31), + .field_ddc_slave_addr = REG_FIELD(SUN4I_HDMI_DDC_ADDR_REG, 0, 6), + .field_ddc_int_status = REG_FIELD(SUN4I_HDMI_DDC_INT_STATUS_REG, 0, 8), + .field_ddc_fifo_clear = REG_FIELD(SUN4I_HDMI_DDC_FIFO_CTRL_REG, 31, 31), + .field_ddc_fifo_rx_thres = REG_FIELD(SUN4I_HDMI_DDC_FIFO_CTRL_REG, 4, 7), + .field_ddc_fifo_tx_thres = REG_FIELD(SUN4I_HDMI_DDC_FIFO_CTRL_REG, 0, 3), + .field_ddc_byte_count = REG_FIELD(SUN4I_HDMI_DDC_BYTE_COUNT_REG, 0, 9), + .field_ddc_cmd = REG_FIELD(SUN4I_HDMI_DDC_CMD_REG, 0, 2), + .field_ddc_sda_en = REG_FIELD(SUN4I_HDMI_DDC_LINE_CTRL_REG, 9, 9), + .field_ddc_sck_en = REG_FIELD(SUN4I_HDMI_DDC_LINE_CTRL_REG, 8, 8), + + .ddc_fifo_reg = SUN4I_HDMI_DDC_FIFO_DATA_REG, + .ddc_fifo_has_dir = true, +}; + +static const struct sun4i_hdmi_variant sun6i_variant = { + .has_ddc_parent_clk = true, + .has_reset_control = true, + .pad_ctrl0_init_val = 0xff | + SUN4I_HDMI_PAD_CTRL0_TXEN | + SUN4I_HDMI_PAD_CTRL0_CKEN | + SUN4I_HDMI_PAD_CTRL0_PWENG | + SUN4I_HDMI_PAD_CTRL0_PWEND | + SUN4I_HDMI_PAD_CTRL0_PWENC | + SUN4I_HDMI_PAD_CTRL0_LDODEN | + SUN4I_HDMI_PAD_CTRL0_LDOCEN, + .pad_ctrl1_init_val = SUN4I_HDMI_PAD_CTRL1_REG_AMP(6) | + SUN4I_HDMI_PAD_CTRL1_REG_EMP(4) | + SUN4I_HDMI_PAD_CTRL1_REG_DENCK | + SUN4I_HDMI_PAD_CTRL1_REG_DEN | + SUN4I_HDMI_PAD_CTRL1_EMPCK_OPT | + SUN4I_HDMI_PAD_CTRL1_EMP_OPT | + SUN4I_HDMI_PAD_CTRL1_PWSDT | + SUN4I_HDMI_PAD_CTRL1_PWSCK | + SUN4I_HDMI_PAD_CTRL1_AMPCK_OPT | + SUN4I_HDMI_PAD_CTRL1_AMP_OPT | + SUN4I_HDMI_PAD_CTRL1_UNKNOWN, + .pll_ctrl_init_val = SUN4I_HDMI_PLL_CTRL_VCO_S(8) | + SUN4I_HDMI_PLL_CTRL_CS(3) | + SUN4I_HDMI_PLL_CTRL_CP_S(10) | + SUN4I_HDMI_PLL_CTRL_S(4) | + SUN4I_HDMI_PLL_CTRL_VCO_GAIN(4) | + SUN4I_HDMI_PLL_CTRL_SDIV2 | + SUN4I_HDMI_PLL_CTRL_LDO2_EN | + SUN4I_HDMI_PLL_CTRL_LDO1_EN | + SUN4I_HDMI_PLL_CTRL_HV_IS_33 | + SUN4I_HDMI_PLL_CTRL_PLL_EN, + + .ddc_clk_reg = REG_FIELD(SUN6I_HDMI_DDC_CLK_REG, 0, 6), + .ddc_clk_pre_divider = 1, + .ddc_clk_m_offset = 2, + + .tmds_clk_div_offset = 1, + + .field_ddc_en = REG_FIELD(SUN6I_HDMI_DDC_CTRL_REG, 0, 0), + .field_ddc_start = REG_FIELD(SUN6I_HDMI_DDC_CTRL_REG, 27, 27), + .field_ddc_reset = REG_FIELD(SUN6I_HDMI_DDC_CTRL_REG, 31, 31), + .field_ddc_addr_reg = REG_FIELD(SUN6I_HDMI_DDC_ADDR_REG, 1, 31), + .field_ddc_slave_addr = REG_FIELD(SUN6I_HDMI_DDC_ADDR_REG, 1, 7), + .field_ddc_int_status = REG_FIELD(SUN6I_HDMI_DDC_INT_STATUS_REG, 0, 8), + .field_ddc_fifo_clear = REG_FIELD(SUN6I_HDMI_DDC_FIFO_CTRL_REG, 18, 18), + .field_ddc_fifo_rx_thres = REG_FIELD(SUN6I_HDMI_DDC_FIFO_CTRL_REG, 4, 7), + .field_ddc_fifo_tx_thres = REG_FIELD(SUN6I_HDMI_DDC_FIFO_CTRL_REG, 0, 3), + .field_ddc_byte_count = REG_FIELD(SUN6I_HDMI_DDC_CMD_REG, 16, 25), + .field_ddc_cmd = REG_FIELD(SUN6I_HDMI_DDC_CMD_REG, 0, 2), + .field_ddc_sda_en = REG_FIELD(SUN6I_HDMI_DDC_CTRL_REG, 6, 6), + .field_ddc_sck_en = REG_FIELD(SUN6I_HDMI_DDC_CTRL_REG, 4, 4), + + .ddc_fifo_reg = SUN6I_HDMI_DDC_FIFO_DATA_REG, + .ddc_fifo_thres_incl = true, +}; + +static const struct regmap_config sun4i_hdmi_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = 0x580, +}; + static int sun4i_hdmi_bind(struct device *dev, struct device *master, void *data) { @@ -285,6 +459,10 @@ static int sun4i_hdmi_bind(struct device *dev, struct device *master, hdmi->dev = dev; hdmi->drv = drv; + hdmi->variant = of_device_get_match_data(dev); + if (!hdmi->variant) + return -EINVAL; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); hdmi->base = devm_ioremap_resource(dev, res); if (IS_ERR(hdmi->base)) { @@ -292,10 +470,25 @@ static int sun4i_hdmi_bind(struct device *dev, struct device *master, return PTR_ERR(hdmi->base); } + if (hdmi->variant->has_reset_control) { + hdmi->reset = devm_reset_control_get(dev, NULL); + if (IS_ERR(hdmi->reset)) { + dev_err(dev, "Couldn't get the HDMI reset control\n"); + return PTR_ERR(hdmi->reset); + } + + ret = reset_control_deassert(hdmi->reset); + if (ret) { + dev_err(dev, "Couldn't deassert HDMI reset\n"); + return ret; + } + } + hdmi->bus_clk = devm_clk_get(dev, "ahb"); if (IS_ERR(hdmi->bus_clk)) { dev_err(dev, "Couldn't get the HDMI bus clock\n"); - return PTR_ERR(hdmi->bus_clk); + ret = PTR_ERR(hdmi->bus_clk); + goto err_assert_reset; } clk_prepare_enable(hdmi->bus_clk); @@ -321,45 +514,37 @@ static int sun4i_hdmi_bind(struct device *dev, struct device *master, goto err_disable_mod_clk; } + hdmi->regmap = devm_regmap_init_mmio(dev, hdmi->base, + &sun4i_hdmi_regmap_config); + if (IS_ERR(hdmi->regmap)) { + dev_err(dev, "Couldn't create HDMI encoder regmap\n"); + return PTR_ERR(hdmi->regmap); + } + ret = sun4i_tmds_create(hdmi); if (ret) { dev_err(dev, "Couldn't create the TMDS clock\n"); goto err_disable_mod_clk; } + if (hdmi->variant->has_ddc_parent_clk) { + hdmi->ddc_parent_clk = devm_clk_get(dev, "ddc"); + if (IS_ERR(hdmi->ddc_parent_clk)) { + dev_err(dev, "Couldn't get the HDMI DDC clock\n"); + return PTR_ERR(hdmi->ddc_parent_clk); + } + } else { + hdmi->ddc_parent_clk = hdmi->tmds_clk; + } + writel(SUN4I_HDMI_CTRL_ENABLE, hdmi->base + SUN4I_HDMI_CTRL_REG); - writel(SUN4I_HDMI_PAD_CTRL0_TXEN | SUN4I_HDMI_PAD_CTRL0_CKEN | - SUN4I_HDMI_PAD_CTRL0_PWENG | SUN4I_HDMI_PAD_CTRL0_PWEND | - SUN4I_HDMI_PAD_CTRL0_PWENC | SUN4I_HDMI_PAD_CTRL0_LDODEN | - SUN4I_HDMI_PAD_CTRL0_LDOCEN | SUN4I_HDMI_PAD_CTRL0_BIASEN, + writel(hdmi->variant->pad_ctrl0_init_val, hdmi->base + SUN4I_HDMI_PAD_CTRL0_REG); - /* - * We can't just initialize the register there, we need to - * protect the clock bits that have already been read out and - * cached by the clock framework. - */ - reg = readl(hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG); - reg &= SUN4I_HDMI_PAD_CTRL1_HALVE_CLK; - reg |= SUN4I_HDMI_PAD_CTRL1_REG_AMP(6) | - SUN4I_HDMI_PAD_CTRL1_REG_EMP(2) | - SUN4I_HDMI_PAD_CTRL1_REG_DENCK | - SUN4I_HDMI_PAD_CTRL1_REG_DEN | - SUN4I_HDMI_PAD_CTRL1_EMPCK_OPT | - SUN4I_HDMI_PAD_CTRL1_EMP_OPT | - SUN4I_HDMI_PAD_CTRL1_AMPCK_OPT | - SUN4I_HDMI_PAD_CTRL1_AMP_OPT; - writel(reg, hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG); - reg = readl(hdmi->base + SUN4I_HDMI_PLL_CTRL_REG); reg &= SUN4I_HDMI_PLL_CTRL_DIV_MASK; - reg |= SUN4I_HDMI_PLL_CTRL_VCO_S(8) | SUN4I_HDMI_PLL_CTRL_CS(7) | - SUN4I_HDMI_PLL_CTRL_CP_S(15) | SUN4I_HDMI_PLL_CTRL_S(7) | - SUN4I_HDMI_PLL_CTRL_VCO_GAIN(4) | SUN4I_HDMI_PLL_CTRL_SDIV2 | - SUN4I_HDMI_PLL_CTRL_LDO2_EN | SUN4I_HDMI_PLL_CTRL_LDO1_EN | - SUN4I_HDMI_PLL_CTRL_HV_IS_33 | SUN4I_HDMI_PLL_CTRL_BWS | - SUN4I_HDMI_PLL_CTRL_PLL_EN; + reg |= hdmi->variant->pll_ctrl_init_val; writel(reg, hdmi->base + SUN4I_HDMI_PLL_CTRL_REG); ret = sun4i_hdmi_i2c_create(dev, hdmi); @@ -429,6 +614,8 @@ err_disable_mod_clk: clk_disable_unprepare(hdmi->mod_clk); err_disable_bus_clk: clk_disable_unprepare(hdmi->bus_clk); +err_assert_reset: + reset_control_assert(hdmi->reset); return ret; } @@ -463,7 +650,9 @@ static int sun4i_hdmi_remove(struct platform_device *pdev) } static const struct of_device_id sun4i_hdmi_of_table[] = { - { .compatible = "allwinner,sun5i-a10s-hdmi" }, + { .compatible = "allwinner,sun4i-a10-hdmi", .data = &sun4i_variant, }, + { .compatible = "allwinner,sun5i-a10s-hdmi", .data = &sun5i_variant, }, + { .compatible = "allwinner,sun6i-a31-hdmi", .data = &sun6i_variant, }, { } }; MODULE_DEVICE_TABLE(of, sun4i_hdmi_of_table); diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.c index 2e42d09ab42e..58e9d37e8c17 100644 --- a/drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.c +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.c @@ -25,8 +25,6 @@ /* FIFO request bit is set when FIFO level is above RX_THRESHOLD during read */ #define RX_THRESHOLD SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES_MAX -/* FIFO request bit is set when FIFO level is below TX_THRESHOLD during write */ -#define TX_THRESHOLD 1 static int fifo_transfer(struct sun4i_hdmi *hdmi, u8 *buf, int len, bool read) { @@ -39,27 +37,36 @@ static int fifo_transfer(struct sun4i_hdmi *hdmi, u8 *buf, int len, bool read) SUN4I_HDMI_DDC_INT_STATUS_FIFO_REQUEST | SUN4I_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE; u32 reg; + /* + * If threshold is inclusive, then the FIFO may only have + * RX_THRESHOLD number of bytes, instead of RX_THRESHOLD + 1. + */ + int read_len = RX_THRESHOLD + + (hdmi->variant->ddc_fifo_thres_incl ? 0 : 1); - /* Limit transfer length by FIFO threshold */ - len = min_t(int, len, read ? (RX_THRESHOLD + 1) : - (SUN4I_HDMI_DDC_FIFO_SIZE - TX_THRESHOLD + 1)); + /* + * Limit transfer length by FIFO threshold or FIFO size. + * For TX the threshold is for an empty FIFO. + */ + len = min_t(int, len, read ? read_len : SUN4I_HDMI_DDC_FIFO_SIZE); /* Wait until error, FIFO request bit set or transfer complete */ - if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_INT_STATUS_REG, reg, - reg & mask, len * byte_time_ns, 100000)) + if (regmap_field_read_poll_timeout(hdmi->field_ddc_int_status, reg, + reg & mask, len * byte_time_ns, + 100000)) return -ETIMEDOUT; if (reg & SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK) return -EIO; if (read) - readsb(hdmi->base + SUN4I_HDMI_DDC_FIFO_DATA_REG, buf, len); + readsb(hdmi->base + hdmi->variant->ddc_fifo_reg, buf, len); else - writesb(hdmi->base + SUN4I_HDMI_DDC_FIFO_DATA_REG, buf, len); + writesb(hdmi->base + hdmi->variant->ddc_fifo_reg, buf, len); - /* Clear FIFO request bit */ - writel(SUN4I_HDMI_DDC_INT_STATUS_FIFO_REQUEST, - hdmi->base + SUN4I_HDMI_DDC_INT_STATUS_REG); + /* Clear FIFO request bit by forcing a write to that bit */ + regmap_field_force_write(hdmi->field_ddc_int_status, + SUN4I_HDMI_DDC_INT_STATUS_FIFO_REQUEST); return len; } @@ -70,50 +77,52 @@ static int xfer_msg(struct sun4i_hdmi *hdmi, struct i2c_msg *msg) u32 reg; /* Set FIFO direction */ - reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG); - reg &= ~SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK; - reg |= (msg->flags & I2C_M_RD) ? - SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ : - SUN4I_HDMI_DDC_CTRL_FIFO_DIR_WRITE; - writel(reg, hdmi->base + SUN4I_HDMI_DDC_CTRL_REG); + if (hdmi->variant->ddc_fifo_has_dir) { + reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG); + reg &= ~SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK; + reg |= (msg->flags & I2C_M_RD) ? + SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ : + SUN4I_HDMI_DDC_CTRL_FIFO_DIR_WRITE; + writel(reg, hdmi->base + SUN4I_HDMI_DDC_CTRL_REG); + } + + /* Clear address register (not cleared by soft reset) */ + regmap_field_write(hdmi->field_ddc_addr_reg, 0); /* Set I2C address */ - writel(SUN4I_HDMI_DDC_ADDR_SLAVE(msg->addr), - hdmi->base + SUN4I_HDMI_DDC_ADDR_REG); - - /* Set FIFO RX/TX thresholds and clear FIFO */ - reg = readl(hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG); - reg |= SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR; - reg &= ~SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES_MASK; - reg |= SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES(RX_THRESHOLD); - reg &= ~SUN4I_HDMI_DDC_FIFO_CTRL_TX_THRES_MASK; - reg |= SUN4I_HDMI_DDC_FIFO_CTRL_TX_THRES(TX_THRESHOLD); - writel(reg, hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG); - if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG, - reg, - !(reg & SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR), - 100, 2000)) + regmap_field_write(hdmi->field_ddc_slave_addr, msg->addr); + + /* + * Set FIFO RX/TX thresholds and clear FIFO + * + * If threshold is inclusive, we can set the TX threshold to + * 0 instead of 1. + */ + regmap_field_write(hdmi->field_ddc_fifo_tx_thres, + hdmi->variant->ddc_fifo_thres_incl ? 0 : 1); + regmap_field_write(hdmi->field_ddc_fifo_rx_thres, RX_THRESHOLD); + regmap_field_write(hdmi->field_ddc_fifo_clear, 1); + if (regmap_field_read_poll_timeout(hdmi->field_ddc_fifo_clear, + reg, !reg, 100, 2000)) return -EIO; /* Set transfer length */ - writel(msg->len, hdmi->base + SUN4I_HDMI_DDC_BYTE_COUNT_REG); + regmap_field_write(hdmi->field_ddc_byte_count, msg->len); /* Set command */ - writel(msg->flags & I2C_M_RD ? - SUN4I_HDMI_DDC_CMD_IMPLICIT_READ : - SUN4I_HDMI_DDC_CMD_IMPLICIT_WRITE, - hdmi->base + SUN4I_HDMI_DDC_CMD_REG); + regmap_field_write(hdmi->field_ddc_cmd, + msg->flags & I2C_M_RD ? + SUN4I_HDMI_DDC_CMD_IMPLICIT_READ : + SUN4I_HDMI_DDC_CMD_IMPLICIT_WRITE); - /* Clear interrupt status bits */ - writel(SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK | - SUN4I_HDMI_DDC_INT_STATUS_FIFO_REQUEST | - SUN4I_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE, - hdmi->base + SUN4I_HDMI_DDC_INT_STATUS_REG); + /* Clear interrupt status bits by forcing a write */ + regmap_field_force_write(hdmi->field_ddc_int_status, + SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK | + SUN4I_HDMI_DDC_INT_STATUS_FIFO_REQUEST | + SUN4I_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE); /* Start command */ - reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG); - writel(reg | SUN4I_HDMI_DDC_CTRL_START_CMD, - hdmi->base + SUN4I_HDMI_DDC_CTRL_REG); + regmap_field_write(hdmi->field_ddc_start, 1); /* Transfer bytes */ for (i = 0; i < msg->len; i += len) { @@ -124,14 +133,12 @@ static int xfer_msg(struct sun4i_hdmi *hdmi, struct i2c_msg *msg) } /* Wait for command to finish */ - if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG, - reg, - !(reg & SUN4I_HDMI_DDC_CTRL_START_CMD), - 100, 100000)) + if (regmap_field_read_poll_timeout(hdmi->field_ddc_start, + reg, !reg, 100, 100000)) return -EIO; /* Check for errors */ - reg = readl(hdmi->base + SUN4I_HDMI_DDC_INT_STATUS_REG); + regmap_field_read(hdmi->field_ddc_int_status, ®); if ((reg & SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK) || !(reg & SUN4I_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE)) { return -EIO; @@ -154,20 +161,21 @@ static int sun4i_hdmi_i2c_xfer(struct i2c_adapter *adap, return -EINVAL; } + /* DDC clock needs to be enabled for the module to work */ + clk_prepare_enable(hdmi->ddc_clk); + clk_set_rate(hdmi->ddc_clk, 100000); + /* Reset I2C controller */ - writel(SUN4I_HDMI_DDC_CTRL_ENABLE | SUN4I_HDMI_DDC_CTRL_RESET, - hdmi->base + SUN4I_HDMI_DDC_CTRL_REG); - if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG, reg, - !(reg & SUN4I_HDMI_DDC_CTRL_RESET), - 100, 2000)) + regmap_field_write(hdmi->field_ddc_en, 1); + regmap_field_write(hdmi->field_ddc_reset, 1); + if (regmap_field_read_poll_timeout(hdmi->field_ddc_reset, + reg, !reg, 100, 2000)) { + clk_disable_unprepare(hdmi->ddc_clk); return -EIO; + } - writel(SUN4I_HDMI_DDC_LINE_CTRL_SDA_ENABLE | - SUN4I_HDMI_DDC_LINE_CTRL_SCL_ENABLE, - hdmi->base + SUN4I_HDMI_DDC_LINE_CTRL_REG); - - clk_prepare_enable(hdmi->ddc_clk); - clk_set_rate(hdmi->ddc_clk, 100000); + regmap_field_write(hdmi->field_ddc_sck_en, 1); + regmap_field_write(hdmi->field_ddc_sda_en, 1); for (i = 0; i < num; i++) { err = xfer_msg(hdmi, &msgs[i]); @@ -191,12 +199,105 @@ static const struct i2c_algorithm sun4i_hdmi_i2c_algorithm = { .functionality = sun4i_hdmi_i2c_func, }; +static int sun4i_hdmi_init_regmap_fields(struct sun4i_hdmi *hdmi) +{ + hdmi->field_ddc_en = + devm_regmap_field_alloc(hdmi->dev, hdmi->regmap, + hdmi->variant->field_ddc_en); + if (IS_ERR(hdmi->field_ddc_en)) + return PTR_ERR(hdmi->field_ddc_en); + + hdmi->field_ddc_start = + devm_regmap_field_alloc(hdmi->dev, hdmi->regmap, + hdmi->variant->field_ddc_start); + if (IS_ERR(hdmi->field_ddc_start)) + return PTR_ERR(hdmi->field_ddc_start); + + hdmi->field_ddc_reset = + devm_regmap_field_alloc(hdmi->dev, hdmi->regmap, + hdmi->variant->field_ddc_reset); + if (IS_ERR(hdmi->field_ddc_reset)) + return PTR_ERR(hdmi->field_ddc_reset); + + hdmi->field_ddc_addr_reg = + devm_regmap_field_alloc(hdmi->dev, hdmi->regmap, + hdmi->variant->field_ddc_addr_reg); + if (IS_ERR(hdmi->field_ddc_addr_reg)) + return PTR_ERR(hdmi->field_ddc_addr_reg); + + hdmi->field_ddc_slave_addr = + devm_regmap_field_alloc(hdmi->dev, hdmi->regmap, + hdmi->variant->field_ddc_slave_addr); + if (IS_ERR(hdmi->field_ddc_slave_addr)) + return PTR_ERR(hdmi->field_ddc_slave_addr); + + hdmi->field_ddc_int_mask = + devm_regmap_field_alloc(hdmi->dev, hdmi->regmap, + hdmi->variant->field_ddc_int_mask); + if (IS_ERR(hdmi->field_ddc_int_mask)) + return PTR_ERR(hdmi->field_ddc_int_mask); + + hdmi->field_ddc_int_status = + devm_regmap_field_alloc(hdmi->dev, hdmi->regmap, + hdmi->variant->field_ddc_int_status); + if (IS_ERR(hdmi->field_ddc_int_status)) + return PTR_ERR(hdmi->field_ddc_int_status); + + hdmi->field_ddc_fifo_clear = + devm_regmap_field_alloc(hdmi->dev, hdmi->regmap, + hdmi->variant->field_ddc_fifo_clear); + if (IS_ERR(hdmi->field_ddc_fifo_clear)) + return PTR_ERR(hdmi->field_ddc_fifo_clear); + + hdmi->field_ddc_fifo_rx_thres = + devm_regmap_field_alloc(hdmi->dev, hdmi->regmap, + hdmi->variant->field_ddc_fifo_rx_thres); + if (IS_ERR(hdmi->field_ddc_fifo_rx_thres)) + return PTR_ERR(hdmi->field_ddc_fifo_rx_thres); + + hdmi->field_ddc_fifo_tx_thres = + devm_regmap_field_alloc(hdmi->dev, hdmi->regmap, + hdmi->variant->field_ddc_fifo_tx_thres); + if (IS_ERR(hdmi->field_ddc_fifo_tx_thres)) + return PTR_ERR(hdmi->field_ddc_fifo_tx_thres); + + hdmi->field_ddc_byte_count = + devm_regmap_field_alloc(hdmi->dev, hdmi->regmap, + hdmi->variant->field_ddc_byte_count); + if (IS_ERR(hdmi->field_ddc_byte_count)) + return PTR_ERR(hdmi->field_ddc_byte_count); + + hdmi->field_ddc_cmd = + devm_regmap_field_alloc(hdmi->dev, hdmi->regmap, + hdmi->variant->field_ddc_cmd); + if (IS_ERR(hdmi->field_ddc_cmd)) + return PTR_ERR(hdmi->field_ddc_cmd); + + hdmi->field_ddc_sda_en = + devm_regmap_field_alloc(hdmi->dev, hdmi->regmap, + hdmi->variant->field_ddc_sda_en); + if (IS_ERR(hdmi->field_ddc_sda_en)) + return PTR_ERR(hdmi->field_ddc_sda_en); + + hdmi->field_ddc_sck_en = + devm_regmap_field_alloc(hdmi->dev, hdmi->regmap, + hdmi->variant->field_ddc_sck_en); + if (IS_ERR(hdmi->field_ddc_sck_en)) + return PTR_ERR(hdmi->field_ddc_sck_en); + + return 0; +} + int sun4i_hdmi_i2c_create(struct device *dev, struct sun4i_hdmi *hdmi) { struct i2c_adapter *adap; int ret = 0; - ret = sun4i_ddc_create(hdmi, hdmi->tmds_clk); + ret = sun4i_ddc_create(hdmi, hdmi->ddc_parent_clk); + if (ret) + return ret; + + ret = sun4i_hdmi_init_regmap_fields(hdmi); if (ret) return ret; diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c index 5cf2527bffc8..dc332ea56f6c 100644 --- a/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c @@ -12,12 +12,13 @@ #include <linux/clk-provider.h> -#include "sun4i_tcon.h" #include "sun4i_hdmi.h" struct sun4i_tmds { struct clk_hw hw; struct sun4i_hdmi *hdmi; + + u8 div_offset; }; static inline struct sun4i_tmds *hw_to_tmds(struct clk_hw *hw) @@ -28,6 +29,7 @@ static inline struct sun4i_tmds *hw_to_tmds(struct clk_hw *hw) static unsigned long sun4i_tmds_calc_divider(unsigned long rate, unsigned long parent_rate, + u8 div_offset, u8 *div, bool *half) { @@ -35,7 +37,7 @@ static unsigned long sun4i_tmds_calc_divider(unsigned long rate, u8 best_m = 0, m; bool is_double; - for (m = 1; m < 16; m++) { + for (m = div_offset ?: 1; m < (16 + div_offset); m++) { u8 d; for (d = 1; d < 3; d++) { @@ -67,11 +69,12 @@ static unsigned long sun4i_tmds_calc_divider(unsigned long rate, static int sun4i_tmds_determine_rate(struct clk_hw *hw, struct clk_rate_request *req) { - struct clk_hw *parent; + struct sun4i_tmds *tmds = hw_to_tmds(hw); + struct clk_hw *parent = NULL; unsigned long best_parent = 0; unsigned long rate = req->rate; int best_div = 1, best_half = 1; - int i, j; + int i, j, p; /* * We only consider PLL3, since the TCON is very likely to be @@ -79,32 +82,38 @@ static int sun4i_tmds_determine_rate(struct clk_hw *hw, * clock, so we should not need to do anything. */ - parent = clk_hw_get_parent_by_index(hw, 0); - if (!parent) - return -EINVAL; - - for (i = 1; i < 3; i++) { - for (j = 1; j < 16; j++) { - unsigned long ideal = rate * i * j; - unsigned long rounded; - - rounded = clk_hw_round_rate(parent, ideal); - - if (rounded == ideal) { - best_parent = rounded; - best_half = i; - best_div = j; - goto out; - } - - if (abs(rate - rounded / i) < - abs(rate - best_parent / best_div)) { - best_parent = rounded; - best_div = i; + for (p = 0; p < clk_hw_get_num_parents(hw); p++) { + parent = clk_hw_get_parent_by_index(hw, p); + if (!parent) + continue; + + for (i = 1; i < 3; i++) { + for (j = tmds->div_offset ?: 1; + j < (16 + tmds->div_offset); j++) { + unsigned long ideal = rate * i * j; + unsigned long rounded; + + rounded = clk_hw_round_rate(parent, ideal); + + if (rounded == ideal) { + best_parent = rounded; + best_half = i; + best_div = j; + goto out; + } + + if (abs(rate - rounded / i) < + abs(rate - best_parent / best_div)) { + best_parent = rounded; + best_div = i; + } } } } + if (!parent) + return -EINVAL; + out: req->rate = best_parent / best_half / best_div; req->best_parent_rate = best_parent; @@ -124,7 +133,7 @@ static unsigned long sun4i_tmds_recalc_rate(struct clk_hw *hw, parent_rate /= 2; reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG); - reg = (reg >> 4) & 0xf; + reg = ((reg >> 4) & 0xf) + tmds->div_offset; if (!reg) reg = 1; @@ -139,7 +148,8 @@ static int sun4i_tmds_set_rate(struct clk_hw *hw, unsigned long rate, u32 reg; u8 div; - sun4i_tmds_calc_divider(rate, parent_rate, &div, &half); + sun4i_tmds_calc_divider(rate, parent_rate, tmds->div_offset, + &div, &half); reg = readl(tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG); reg &= ~SUN4I_HDMI_PAD_CTRL1_HALVE_CLK; @@ -149,7 +159,7 @@ static int sun4i_tmds_set_rate(struct clk_hw *hw, unsigned long rate, reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG); reg &= ~SUN4I_HDMI_PLL_CTRL_DIV_MASK; - writel(reg | SUN4I_HDMI_PLL_CTRL_DIV(div), + writel(reg | SUN4I_HDMI_PLL_CTRL_DIV(div - tmds->div_offset), tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG); return 0; @@ -216,6 +226,7 @@ int sun4i_tmds_create(struct sun4i_hdmi *hdmi) tmds->hdmi = hdmi; tmds->hw.init = &init; + tmds->div_offset = hdmi->variant->tmds_clk_div_offset; hdmi->tmds_clk = devm_clk_register(hdmi->dev, &tmds->hw); if (IS_ERR(hdmi->tmds_clk)) diff --git a/drivers/gpu/drm/sun4i/sun4i_rgb.c b/drivers/gpu/drm/sun4i/sun4i_rgb.c index 7cd7090ad63a..832f8f9bc47f 100644 --- a/drivers/gpu/drm/sun4i/sun4i_rgb.c +++ b/drivers/gpu/drm/sun4i/sun4i_rgb.c @@ -134,13 +134,10 @@ static void sun4i_rgb_encoder_enable(struct drm_encoder *encoder) DRM_DEBUG_DRIVER("Enabling RGB output\n"); - if (!IS_ERR(tcon->panel)) + if (!IS_ERR(tcon->panel)) { drm_panel_prepare(tcon->panel); - - sun4i_tcon_channel_enable(tcon, 0); - - if (!IS_ERR(tcon->panel)) drm_panel_enable(tcon->panel); + } } static void sun4i_rgb_encoder_disable(struct drm_encoder *encoder) @@ -150,31 +147,13 @@ static void sun4i_rgb_encoder_disable(struct drm_encoder *encoder) DRM_DEBUG_DRIVER("Disabling RGB output\n"); - if (!IS_ERR(tcon->panel)) + if (!IS_ERR(tcon->panel)) { drm_panel_disable(tcon->panel); - - sun4i_tcon_channel_disable(tcon, 0); - - if (!IS_ERR(tcon->panel)) drm_panel_unprepare(tcon->panel); -} - -static void sun4i_rgb_encoder_mode_set(struct drm_encoder *encoder, - struct drm_display_mode *mode, - struct drm_display_mode *adjusted_mode) -{ - struct sun4i_rgb *rgb = drm_encoder_to_sun4i_rgb(encoder); - struct sun4i_tcon *tcon = rgb->tcon; - - sun4i_tcon0_mode_set(tcon, mode); - sun4i_tcon_set_mux(tcon, 0, encoder); - - /* FIXME: This seems to be board specific */ - clk_set_phase(tcon->dclk, 120); + } } static struct drm_encoder_helper_funcs sun4i_rgb_enc_helper_funcs = { - .mode_set = sun4i_rgb_encoder_mode_set, .disable = sun4i_rgb_encoder_disable, .enable = sun4i_rgb_encoder_enable, }; diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c index d9791292553e..e122f5b2a395 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.c +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c @@ -14,9 +14,12 @@ #include <drm/drm_atomic_helper.h> #include <drm/drm_crtc.h> #include <drm/drm_crtc_helper.h> +#include <drm/drm_encoder.h> #include <drm/drm_modes.h> #include <drm/drm_of.h> +#include <uapi/drm/drm_mode.h> + #include <linux/component.h> #include <linux/ioport.h> #include <linux/of_address.h> @@ -32,66 +35,61 @@ #include "sun4i_tcon.h" #include "sunxi_engine.h" -void sun4i_tcon_disable(struct sun4i_tcon *tcon) -{ - DRM_DEBUG_DRIVER("Disabling TCON\n"); - - /* Disable the TCON */ - regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG, - SUN4I_TCON_GCTL_TCON_ENABLE, 0); -} -EXPORT_SYMBOL(sun4i_tcon_disable); - -void sun4i_tcon_enable(struct sun4i_tcon *tcon) +static void sun4i_tcon_channel_set_status(struct sun4i_tcon *tcon, int channel, + bool enabled) { - DRM_DEBUG_DRIVER("Enabling TCON\n"); - - /* Enable the TCON */ - regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG, - SUN4I_TCON_GCTL_TCON_ENABLE, - SUN4I_TCON_GCTL_TCON_ENABLE); -} -EXPORT_SYMBOL(sun4i_tcon_enable); + struct clk *clk; -void sun4i_tcon_channel_disable(struct sun4i_tcon *tcon, int channel) -{ - DRM_DEBUG_DRIVER("Disabling TCON channel %d\n", channel); - - /* Disable the TCON's channel */ - if (channel == 0) { + switch (channel) { + case 0: regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG, - SUN4I_TCON0_CTL_TCON_ENABLE, 0); - clk_disable_unprepare(tcon->dclk); + SUN4I_TCON0_CTL_TCON_ENABLE, + enabled ? SUN4I_TCON0_CTL_TCON_ENABLE : 0); + clk = tcon->dclk; + break; + case 1: + WARN_ON(!tcon->quirks->has_channel_1); + regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG, + SUN4I_TCON1_CTL_TCON_ENABLE, + enabled ? SUN4I_TCON1_CTL_TCON_ENABLE : 0); + clk = tcon->sclk1; + break; + default: + DRM_WARN("Unknown channel... doing nothing\n"); return; } - WARN_ON(!tcon->quirks->has_channel_1); - regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG, - SUN4I_TCON1_CTL_TCON_ENABLE, 0); - clk_disable_unprepare(tcon->sclk1); + if (enabled) + clk_prepare_enable(clk); + else + clk_disable_unprepare(clk); } -EXPORT_SYMBOL(sun4i_tcon_channel_disable); -void sun4i_tcon_channel_enable(struct sun4i_tcon *tcon, int channel) +void sun4i_tcon_set_status(struct sun4i_tcon *tcon, + const struct drm_encoder *encoder, + bool enabled) { - DRM_DEBUG_DRIVER("Enabling TCON channel %d\n", channel); - - /* Enable the TCON's channel */ - if (channel == 0) { - regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG, - SUN4I_TCON0_CTL_TCON_ENABLE, - SUN4I_TCON0_CTL_TCON_ENABLE); - clk_prepare_enable(tcon->dclk); + int channel; + + switch (encoder->encoder_type) { + case DRM_MODE_ENCODER_NONE: + channel = 0; + break; + case DRM_MODE_ENCODER_TMDS: + case DRM_MODE_ENCODER_TVDAC: + channel = 1; + break; + default: + DRM_DEBUG_DRIVER("Unknown encoder type, doing nothing...\n"); return; } - WARN_ON(!tcon->quirks->has_channel_1); - regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG, - SUN4I_TCON1_CTL_TCON_ENABLE, - SUN4I_TCON1_CTL_TCON_ENABLE); - clk_prepare_enable(tcon->sclk1); + regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG, + SUN4I_TCON_GCTL_TCON_ENABLE, + enabled ? SUN4I_TCON_GCTL_TCON_ENABLE : 0); + + sun4i_tcon_channel_set_status(tcon, channel, enabled); } -EXPORT_SYMBOL(sun4i_tcon_channel_enable); void sun4i_tcon_enable_vblank(struct sun4i_tcon *tcon, bool enable) { @@ -109,30 +107,40 @@ void sun4i_tcon_enable_vblank(struct sun4i_tcon *tcon, bool enable) } EXPORT_SYMBOL(sun4i_tcon_enable_vblank); -void sun4i_tcon_set_mux(struct sun4i_tcon *tcon, int channel, - struct drm_encoder *encoder) +/* + * This function is a helper for TCON output muxing. The TCON output + * muxing control register in earlier SoCs (without the TCON TOP block) + * are located in TCON0. This helper returns a pointer to TCON0's + * sun4i_tcon structure, or NULL if not found. + */ +static struct sun4i_tcon *sun4i_get_tcon0(struct drm_device *drm) { - u32 val; + struct sun4i_drv *drv = drm->dev_private; + struct sun4i_tcon *tcon; - if (!tcon->quirks->has_unknown_mux) - return; + list_for_each_entry(tcon, &drv->tcon_list, list) + if (tcon->id == 0) + return tcon; - if (channel != 1) - return; + dev_warn(drm->dev, + "TCON0 not found, display output muxing may not work\n"); - if (encoder->encoder_type == DRM_MODE_ENCODER_TVDAC) - val = 1; - else - val = 0; + return NULL; +} - /* - * FIXME: Undocumented bits - */ - regmap_write(tcon->regs, SUN4I_TCON_MUX_CTRL_REG, val); +void sun4i_tcon_set_mux(struct sun4i_tcon *tcon, int channel, + const struct drm_encoder *encoder) +{ + int ret = -ENOTSUPP; + + if (tcon->quirks->set_mux) + ret = tcon->quirks->set_mux(tcon, encoder); + + DRM_DEBUG_DRIVER("Muxing encoder %s to CRTC %s: %d\n", + encoder->name, encoder->crtc->name, ret); } -EXPORT_SYMBOL(sun4i_tcon_set_mux); -static int sun4i_tcon_get_clk_delay(struct drm_display_mode *mode, +static int sun4i_tcon_get_clk_delay(const struct drm_display_mode *mode, int channel) { int delay = mode->vtotal - mode->vdisplay; @@ -150,15 +158,26 @@ static int sun4i_tcon_get_clk_delay(struct drm_display_mode *mode, return delay; } -void sun4i_tcon0_mode_set(struct sun4i_tcon *tcon, - struct drm_display_mode *mode) +static void sun4i_tcon0_mode_set_common(struct sun4i_tcon *tcon, + const struct drm_display_mode *mode) +{ + /* Configure the dot clock */ + clk_set_rate(tcon->dclk, mode->crtc_clock * 1000); + + /* Set the resolution */ + regmap_write(tcon->regs, SUN4I_TCON0_BASIC0_REG, + SUN4I_TCON0_BASIC0_X(mode->crtc_hdisplay) | + SUN4I_TCON0_BASIC0_Y(mode->crtc_vdisplay)); +} + +static void sun4i_tcon0_mode_set_rgb(struct sun4i_tcon *tcon, + const struct drm_display_mode *mode) { unsigned int bp, hsync, vsync; u8 clk_delay; u32 val = 0; - /* Configure the dot clock */ - clk_set_rate(tcon->dclk, mode->crtc_clock * 1000); + sun4i_tcon0_mode_set_common(tcon, mode); /* Adjust clock delay */ clk_delay = sun4i_tcon_get_clk_delay(mode, 0); @@ -166,11 +185,6 @@ void sun4i_tcon0_mode_set(struct sun4i_tcon *tcon, SUN4I_TCON0_CTL_CLK_DELAY_MASK, SUN4I_TCON0_CTL_CLK_DELAY(clk_delay)); - /* Set the resolution */ - regmap_write(tcon->regs, SUN4I_TCON0_BASIC0_REG, - SUN4I_TCON0_BASIC0_X(mode->crtc_hdisplay) | - SUN4I_TCON0_BASIC0_Y(mode->crtc_vdisplay)); - /* * This is called a backporch in the register documentation, * but it really is the back porch + hsync @@ -224,10 +238,9 @@ void sun4i_tcon0_mode_set(struct sun4i_tcon *tcon, /* Enable the output on the pins */ regmap_write(tcon->regs, SUN4I_TCON0_IO_TRI_REG, 0); } -EXPORT_SYMBOL(sun4i_tcon0_mode_set); -void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon, - struct drm_display_mode *mode) +static void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon, + const struct drm_display_mode *mode) { unsigned int bp, hsync, vsync, vtotal; u8 clk_delay; @@ -315,7 +328,26 @@ void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon, SUN4I_TCON_GCTL_IOMAP_MASK, SUN4I_TCON_GCTL_IOMAP_TCON1); } -EXPORT_SYMBOL(sun4i_tcon1_mode_set); + +void sun4i_tcon_mode_set(struct sun4i_tcon *tcon, + const struct drm_encoder *encoder, + const struct drm_display_mode *mode) +{ + switch (encoder->encoder_type) { + case DRM_MODE_ENCODER_NONE: + sun4i_tcon0_mode_set_rgb(tcon, mode); + sun4i_tcon_set_mux(tcon, 0, encoder); + break; + case DRM_MODE_ENCODER_TVDAC: + case DRM_MODE_ENCODER_TMDS: + sun4i_tcon1_mode_set(tcon, mode); + sun4i_tcon_set_mux(tcon, 1, encoder); + break; + default: + DRM_DEBUG_DRIVER("Unknown encoder type, doing nothing...\n"); + } +} +EXPORT_SYMBOL(sun4i_tcon_mode_set); static void sun4i_tcon_finish_page_flip(struct drm_device *dev, struct sun4i_crtc *scrtc) @@ -463,42 +495,170 @@ static int sun4i_tcon_init_regmap(struct device *dev, * function in fact searches the corresponding engine, and the ID is * requested via the get_id function of the engine. */ -static struct sunxi_engine *sun4i_tcon_find_engine(struct sun4i_drv *drv, - struct device_node *node) +static struct sunxi_engine * +sun4i_tcon_find_engine_traverse(struct sun4i_drv *drv, + struct device_node *node) { struct device_node *port, *ep, *remote; - struct sunxi_engine *engine; + struct sunxi_engine *engine = ERR_PTR(-EINVAL); port = of_graph_get_port_by_id(node, 0); if (!port) return ERR_PTR(-EINVAL); + /* + * This only works if there is only one path from the TCON + * to any display engine. Otherwise the probe order of the + * TCONs and display engines is not guaranteed. They may + * either bind to the wrong one, or worse, bind to the same + * one if additional checks are not done. + * + * Bail out if there are multiple input connections. + */ + if (of_get_available_child_count(port) != 1) + goto out_put_port; + + /* Get the first connection without specifying an ID */ + ep = of_get_next_available_child(port, NULL); + if (!ep) + goto out_put_port; + + remote = of_graph_get_remote_port_parent(ep); + if (!remote) + goto out_put_ep; + + /* does this node match any registered engines? */ + list_for_each_entry(engine, &drv->engine_list, list) + if (remote == engine->node) + goto out_put_remote; + + /* keep looking through upstream ports */ + engine = sun4i_tcon_find_engine_traverse(drv, remote); + +out_put_remote: + of_node_put(remote); +out_put_ep: + of_node_put(ep); +out_put_port: + of_node_put(port); + + return engine; +} + +/* + * The device tree binding says that the remote endpoint ID of any + * connection between components, up to and including the TCON, of + * the display pipeline should be equal to the actual ID of the local + * component. Thus we can look at any one of the input connections of + * the TCONs, and use that connection's remote endpoint ID as our own. + * + * Since the user of this function already finds the input port, + * the port is passed in directly without further checks. + */ +static int sun4i_tcon_of_get_id_from_port(struct device_node *port) +{ + struct device_node *ep; + int ret = -EINVAL; + + /* try finding an upstream endpoint */ for_each_available_child_of_node(port, ep) { - remote = of_graph_get_remote_port_parent(ep); + struct device_node *remote; + u32 reg; + + remote = of_graph_get_remote_endpoint(ep); if (!remote) continue; - /* does this node match any registered engines? */ - list_for_each_entry(engine, &drv->engine_list, list) { - if (remote == engine->node) { - of_node_put(remote); - of_node_put(port); - return engine; - } - } + ret = of_property_read_u32(remote, "reg", ®); + if (ret) + continue; - /* keep looking through upstream ports */ - engine = sun4i_tcon_find_engine(drv, remote); - if (!IS_ERR(engine)) { - of_node_put(remote); - of_node_put(port); - return engine; - } + ret = reg; } + return ret; +} + +/* + * Once we know the TCON's id, we can look through the list of + * engines to find a matching one. We assume all engines have + * been probed and added to the list. + */ +static struct sunxi_engine *sun4i_tcon_get_engine_by_id(struct sun4i_drv *drv, + int id) +{ + struct sunxi_engine *engine; + + list_for_each_entry(engine, &drv->engine_list, list) + if (engine->id == id) + return engine; + return ERR_PTR(-EINVAL); } +/* + * On SoCs with the old display pipeline design (Display Engine 1.0), + * we assumed the TCON was always tied to just one backend. However + * this proved not to be the case. On the A31, the TCON can select + * either backend as its source. On the A20 (and likely on the A10), + * the backend can choose which TCON to output to. + * + * The device tree binding says that the remote endpoint ID of any + * connection between components, up to and including the TCON, of + * the display pipeline should be equal to the actual ID of the local + * component. Thus we should be able to look at any one of the input + * connections of the TCONs, and use that connection's remote endpoint + * ID as our own. + * + * However the connections between the backend and TCON were assumed + * to be always singular, and their endpoit IDs were all incorrectly + * set to 0. This means for these old device trees, we cannot just look + * up the remote endpoint ID of a TCON input endpoint. TCON1 would be + * incorrectly identified as TCON0. + * + * This function first checks if the TCON node has 2 input endpoints. + * If so, then the device tree is a corrected version, and it will use + * sun4i_tcon_of_get_id() and sun4i_tcon_get_engine_by_id() from above + * to fetch the ID and engine directly. If not, then it is likely an + * old device trees, where the endpoint IDs were incorrect, but did not + * have endpoint connections between the backend and TCON across + * different display pipelines. It will fall back to the old method of + * traversing the of_graph to try and find a matching engine by device + * node. + * + * In the case of single display pipeline device trees, either method + * works. + */ +static struct sunxi_engine *sun4i_tcon_find_engine(struct sun4i_drv *drv, + struct device_node *node) +{ + struct device_node *port; + struct sunxi_engine *engine; + + port = of_graph_get_port_by_id(node, 0); + if (!port) + return ERR_PTR(-EINVAL); + + /* + * Is this a corrected device tree with cross pipeline + * connections between the backend and TCON? + */ + if (of_get_child_count(port) > 1) { + /* Get our ID directly from an upstream endpoint */ + int id = sun4i_tcon_of_get_id_from_port(port); + + /* Get our engine by matching our ID */ + engine = sun4i_tcon_get_engine_by_id(drv, id); + + of_node_put(port); + return engine; + } + + /* Fallback to old method by traversing input endpoints */ + of_node_put(port); + return sun4i_tcon_find_engine_traverse(drv, node); +} + static int sun4i_tcon_bind(struct device *dev, struct device *master, void *data) { @@ -530,10 +690,7 @@ static int sun4i_tcon_bind(struct device *dev, struct device *master, } /* Make sure our TCON is reset */ - if (!reset_control_status(tcon->lcd_rst)) - reset_control_assert(tcon->lcd_rst); - - ret = reset_control_deassert(tcon->lcd_rst); + ret = reset_control_reset(tcon->lcd_rst); if (ret) { dev_err(dev, "Couldn't deassert our reset line\n"); return ret; @@ -574,6 +731,25 @@ static int sun4i_tcon_bind(struct device *dev, struct device *master, if (ret < 0) goto err_free_clocks; + if (tcon->quirks->needs_de_be_mux) { + /* + * We assume there is no dynamic muxing of backends + * and TCONs, so we select the backend with same ID. + * + * While dynamic selection might be interesting, since + * the CRTC is tied to the TCON, while the layers are + * tied to the backends, this means, we will need to + * switch between groups of layers. There might not be + * a way to represent this constraint in DRM. + */ + regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG, + SUN4I_TCON0_CTL_SRC_SEL_MASK, + tcon->id); + regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG, + SUN4I_TCON1_CTL_SRC_SEL_MASK, + tcon->id); + } + list_add_tail(&tcon->list, &drv->tcon_list); return 0; @@ -623,17 +799,97 @@ static int sun4i_tcon_remove(struct platform_device *pdev) return 0; } +/* platform specific TCON muxing callbacks */ +static int sun4i_a10_tcon_set_mux(struct sun4i_tcon *tcon, + const struct drm_encoder *encoder) +{ + struct sun4i_tcon *tcon0 = sun4i_get_tcon0(encoder->dev); + u32 shift; + + if (!tcon0) + return -EINVAL; + + switch (encoder->encoder_type) { + case DRM_MODE_ENCODER_TMDS: + /* HDMI */ + shift = 8; + break; + default: + return -EINVAL; + } + + regmap_update_bits(tcon0->regs, SUN4I_TCON_MUX_CTRL_REG, + 0x3 << shift, tcon->id << shift); + + return 0; +} + +static int sun5i_a13_tcon_set_mux(struct sun4i_tcon *tcon, + const struct drm_encoder *encoder) +{ + u32 val; + + if (encoder->encoder_type == DRM_MODE_ENCODER_TVDAC) + val = 1; + else + val = 0; + + /* + * FIXME: Undocumented bits + */ + return regmap_write(tcon->regs, SUN4I_TCON_MUX_CTRL_REG, val); +} + +static int sun6i_tcon_set_mux(struct sun4i_tcon *tcon, + const struct drm_encoder *encoder) +{ + struct sun4i_tcon *tcon0 = sun4i_get_tcon0(encoder->dev); + u32 shift; + + if (!tcon0) + return -EINVAL; + + switch (encoder->encoder_type) { + case DRM_MODE_ENCODER_TMDS: + /* HDMI */ + shift = 8; + break; + default: + /* TODO A31 has MIPI DSI but A31s does not */ + return -EINVAL; + } + + regmap_update_bits(tcon0->regs, SUN4I_TCON_MUX_CTRL_REG, + 0x3 << shift, tcon->id << shift); + + return 0; +} + +static const struct sun4i_tcon_quirks sun4i_a10_quirks = { + .has_channel_1 = true, + .set_mux = sun4i_a10_tcon_set_mux, +}; + static const struct sun4i_tcon_quirks sun5i_a13_quirks = { - .has_unknown_mux = true, - .has_channel_1 = true, + .has_channel_1 = true, + .set_mux = sun5i_a13_tcon_set_mux, }; static const struct sun4i_tcon_quirks sun6i_a31_quirks = { - .has_channel_1 = true, + .has_channel_1 = true, + .needs_de_be_mux = true, + .set_mux = sun6i_tcon_set_mux, }; static const struct sun4i_tcon_quirks sun6i_a31s_quirks = { - .has_channel_1 = true, + .has_channel_1 = true, + .needs_de_be_mux = true, +}; + +static const struct sun4i_tcon_quirks sun7i_a20_quirks = { + .has_channel_1 = true, + /* Same display pipeline structure as A10 */ + .set_mux = sun4i_a10_tcon_set_mux, }; static const struct sun4i_tcon_quirks sun8i_a33_quirks = { @@ -645,9 +901,11 @@ static const struct sun4i_tcon_quirks sun8i_v3s_quirks = { }; static const struct of_device_id sun4i_tcon_of_table[] = { + { .compatible = "allwinner,sun4i-a10-tcon", .data = &sun4i_a10_quirks }, { .compatible = "allwinner,sun5i-a13-tcon", .data = &sun5i_a13_quirks }, { .compatible = "allwinner,sun6i-a31-tcon", .data = &sun6i_a31_quirks }, { .compatible = "allwinner,sun6i-a31s-tcon", .data = &sun6i_a31s_quirks }, + { .compatible = "allwinner,sun7i-a20-tcon", .data = &sun7i_a20_quirks }, { .compatible = "allwinner,sun8i-a33-tcon", .data = &sun8i_a33_quirks }, { .compatible = "allwinner,sun8i-v3s-tcon", .data = &sun8i_v3s_quirks }, { } diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.h b/drivers/gpu/drm/sun4i/sun4i_tcon.h index 552c88ec16be..f61bf6d83b4a 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.h +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.h @@ -37,6 +37,7 @@ #define SUN4I_TCON0_CTL_TCON_ENABLE BIT(31) #define SUN4I_TCON0_CTL_CLK_DELAY_MASK GENMASK(8, 4) #define SUN4I_TCON0_CTL_CLK_DELAY(delay) ((delay << 4) & SUN4I_TCON0_CTL_CLK_DELAY_MASK) +#define SUN4I_TCON0_CTL_SRC_SEL_MASK GENMASK(2, 0) #define SUN4I_TCON0_DCLK_REG 0x44 #define SUN4I_TCON0_DCLK_GATE_BIT (31) @@ -85,6 +86,7 @@ #define SUN4I_TCON1_CTL_INTERLACE_ENABLE BIT(20) #define SUN4I_TCON1_CTL_CLK_DELAY_MASK GENMASK(8, 4) #define SUN4I_TCON1_CTL_CLK_DELAY(delay) ((delay << 4) & SUN4I_TCON1_CTL_CLK_DELAY_MASK) +#define SUN4I_TCON1_CTL_SRC_SEL_MASK GENMASK(1, 0) #define SUN4I_TCON1_BASIC0_REG 0x94 #define SUN4I_TCON1_BASIC0_X(width) ((((width) - 1) & 0xfff) << 16) @@ -143,9 +145,14 @@ #define SUN4I_TCON_MAX_CHANNELS 2 +struct sun4i_tcon; + struct sun4i_tcon_quirks { - bool has_unknown_mux; /* sun5i has undocumented mux */ bool has_channel_1; /* a33 does not have channel 1 */ + bool needs_de_be_mux; /* sun6i needs mux to select backend */ + + /* callback to handle tcon muxing options */ + int (*set_mux)(struct sun4i_tcon *, const struct drm_encoder *); }; struct sun4i_tcon { @@ -183,22 +190,11 @@ struct sun4i_tcon { struct drm_bridge *sun4i_tcon_find_bridge(struct device_node *node); struct drm_panel *sun4i_tcon_find_panel(struct device_node *node); -/* Global Control */ -void sun4i_tcon_disable(struct sun4i_tcon *tcon); -void sun4i_tcon_enable(struct sun4i_tcon *tcon); - -/* Channel Control */ -void sun4i_tcon_channel_disable(struct sun4i_tcon *tcon, int channel); -void sun4i_tcon_channel_enable(struct sun4i_tcon *tcon, int channel); - void sun4i_tcon_enable_vblank(struct sun4i_tcon *tcon, bool enable); - -/* Mode Related Controls */ -void sun4i_tcon_set_mux(struct sun4i_tcon *tcon, int channel, - struct drm_encoder *encoder); -void sun4i_tcon0_mode_set(struct sun4i_tcon *tcon, - struct drm_display_mode *mode); -void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon, - struct drm_display_mode *mode); +void sun4i_tcon_mode_set(struct sun4i_tcon *tcon, + const struct drm_encoder *encoder, + const struct drm_display_mode *mode); +void sun4i_tcon_set_status(struct sun4i_tcon *crtc, + const struct drm_encoder *encoder, bool enable); #endif /* __SUN4I_TCON_H__ */ diff --git a/drivers/gpu/drm/sun4i/sun4i_tv.c b/drivers/gpu/drm/sun4i/sun4i_tv.c index 050cfd43c7a0..b070d522ed8d 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tv.c +++ b/drivers/gpu/drm/sun4i/sun4i_tv.c @@ -24,7 +24,6 @@ #include "sun4i_crtc.h" #include "sun4i_drv.h" -#include "sun4i_tcon.h" #include "sunxi_engine.h" #define SUN4I_TVE_EN_REG 0x000 @@ -345,12 +344,9 @@ static void sun4i_tv_disable(struct drm_encoder *encoder) { struct sun4i_tv *tv = drm_encoder_to_sun4i_tv(encoder); struct sun4i_crtc *crtc = drm_crtc_to_sun4i_crtc(encoder->crtc); - struct sun4i_tcon *tcon = crtc->tcon; DRM_DEBUG_DRIVER("Disabling the TV Output\n"); - sun4i_tcon_channel_disable(tcon, 1); - regmap_update_bits(tv->regs, SUN4I_TVE_EN_REG, SUN4I_TVE_EN_ENABLE, 0); @@ -362,7 +358,6 @@ static void sun4i_tv_enable(struct drm_encoder *encoder) { struct sun4i_tv *tv = drm_encoder_to_sun4i_tv(encoder); struct sun4i_crtc *crtc = drm_crtc_to_sun4i_crtc(encoder->crtc); - struct sun4i_tcon *tcon = crtc->tcon; DRM_DEBUG_DRIVER("Enabling the TV Output\n"); @@ -371,8 +366,6 @@ static void sun4i_tv_enable(struct drm_encoder *encoder) regmap_update_bits(tv->regs, SUN4I_TVE_EN_REG, SUN4I_TVE_EN_ENABLE, SUN4I_TVE_EN_ENABLE); - - sun4i_tcon_channel_enable(tcon, 1); } static void sun4i_tv_mode_set(struct drm_encoder *encoder, @@ -380,13 +373,8 @@ static void sun4i_tv_mode_set(struct drm_encoder *encoder, struct drm_display_mode *adjusted_mode) { struct sun4i_tv *tv = drm_encoder_to_sun4i_tv(encoder); - struct sun4i_crtc *crtc = drm_crtc_to_sun4i_crtc(encoder->crtc); - struct sun4i_tcon *tcon = crtc->tcon; const struct tv_mode *tv_mode = sun4i_tv_find_tv_by_mode(mode); - sun4i_tcon1_mode_set(tcon, mode); - sun4i_tcon_set_mux(tcon, 1, encoder); - /* Enable and map the DAC to the output */ regmap_update_bits(tv->regs, SUN4I_TVE_EN_REG, SUN4I_TVE_EN_DAC_MAP_MASK, |