From 5c6cc2bd09b036e85c14880406f95bd1f0dc1734 Mon Sep 17 00:00:00 2001 From: GuEe-GUI <2991707448@qq.com> Date: Thu, 29 Oct 2026 16:06:02 +0800 Subject: [PATCH] [bsp][rockchip] Add RK3588 and RK3528 display and audio support - add RK3588 HDMI controller and HDPTX HDMI PHY support - add RK3528 HDMI PHY, SAI, codec, and audio clock support - extend VOP2 display routing and framebuffer scanout for RK3588 and RK3528 - update Rockchip HDMI, audio card, I2S/TDM, PHY, PM-domain integration - document rk3500 reserved display memory configuration` Signed-off-by: GuEe-GUI <2991707448@qq.com> --- bsp/rockchip/dm/audio/Kconfig | 29 +- bsp/rockchip/dm/audio/SConscript | 11 +- bsp/rockchip/dm/audio/codec/rk3528-codec.c | 389 ++++++ bsp/rockchip/dm/audio/codec/rk3528-codec.h | 58 + bsp/rockchip/dm/audio/codec/rk817-codec.h | 31 + bsp/rockchip/dm/audio/rockchip-audio-card.c | 351 +++++- bsp/rockchip/dm/audio/rockchip-audio-clock.c | 75 ++ bsp/rockchip/dm/audio/rockchip-audio-clock.h | 18 + bsp/rockchip/dm/audio/rockchip-i2s-tdm.c | 247 ++-- bsp/rockchip/dm/audio/rockchip-i2s-tdm.h | 56 +- bsp/rockchip/dm/audio/rockchip-sai.c | 396 ++++++ bsp/rockchip/dm/audio/rockchip-sai.h | 54 + bsp/rockchip/dm/clk/clk-rk3588.c | 2 +- .../dm/graphic/framebuffer/SConscript | 2 +- .../dm/graphic/framebuffer/display-rockchip.c | 135 ++- .../dm/graphic/framebuffer/fb-vop2-vp.c | 457 ++++++- .../dm/graphic/framebuffer/fb-vop2-vp.h | 38 + bsp/rockchip/dm/graphic/framebuffer/fb-vop2.c | 650 ++++++++-- bsp/rockchip/dm/graphic/framebuffer/fb-vop2.h | 116 +- .../dm/graphic/framebuffer/hdmi-rk3588.c | 1063 +++++++++++++++++ .../dm/graphic/framebuffer/hdmi-rockchip.c | 205 +++- bsp/rockchip/dm/include/rockchip-hdmi.h | 3 + bsp/rockchip/dm/mfd/rk8xx.c | 31 +- bsp/rockchip/dm/phye/Kconfig | 18 + bsp/rockchip/dm/phye/SConscript | 6 + .../dm/phye/phye-rockchip-hdptx-hdmi.c | 863 +++++++++++++ .../dm/phye/phye-rockchip-inno-dsidphy.c | 8 +- .../dm/phye/phye-rockchip-inno-hdmi.c | 450 +++++++ .../dm/phye/phye-rockchip-inno-usb2.c | 3 - .../dm/phye/phye-rockchip-mipi-dcphy.c | 11 +- bsp/rockchip/dm/pmdomain/pm-domain-rockchip.c | 2 +- bsp/rockchip/dm/soc/rga2/rga2_drv.c | 1 + bsp/rockchip/rk3500/README.md | 37 + bsp/rockchip/rk3500/README_ZH.md | 33 + .../drivers/include/drivers/phye-hdmi.h | 22 + components/drivers/include/drivers/phye.h | 3 + 36 files changed, 5483 insertions(+), 391 deletions(-) create mode 100644 bsp/rockchip/dm/audio/codec/rk3528-codec.c create mode 100644 bsp/rockchip/dm/audio/codec/rk3528-codec.h create mode 100644 bsp/rockchip/dm/audio/rockchip-audio-clock.c create mode 100644 bsp/rockchip/dm/audio/rockchip-audio-clock.h create mode 100644 bsp/rockchip/dm/audio/rockchip-sai.c create mode 100644 bsp/rockchip/dm/audio/rockchip-sai.h create mode 100644 bsp/rockchip/dm/graphic/framebuffer/hdmi-rk3588.c create mode 100644 bsp/rockchip/dm/phye/phye-rockchip-hdptx-hdmi.c create mode 100644 bsp/rockchip/dm/phye/phye-rockchip-inno-hdmi.c create mode 100644 components/drivers/include/drivers/phye-hdmi.h diff --git a/bsp/rockchip/dm/audio/Kconfig b/bsp/rockchip/dm/audio/Kconfig index 61eec170edc8..9221538da2cd 100755 --- a/bsp/rockchip/dm/audio/Kconfig +++ b/bsp/rockchip/dm/audio/Kconfig @@ -4,9 +4,9 @@ menuconfig RT_AUDIO_ROCKCHIP depends on RT_USING_AUDIO default n help - Rockchip I2S/TDM audio for boards such as ROCK 3A (rk3568): - - Analog output via RK809/RK817 PMIC codec (I2S1) - - HDMI audio via I2S0 (requires dw-hdmi display stack) + Rockchip I2S/TDM and SAI audio for analog and HDMI playback. + RK3528 uses SAI2 with its internal DAC and SAI3 for HDMI. + HDMI audio requires the corresponding display driver. if RT_AUDIO_ROCKCHIP @@ -17,9 +17,18 @@ config RT_AUDIO_ROCKCHIP_I2S_TDM I2S/TDM block driver (e.g. i2s0_8ch, i2s1_8ch). Uses PL330 DMA when RT_USING_DMA is enabled, otherwise PIO. +config RT_AUDIO_ROCKCHIP_SAI + bool "Rockchip RK3528 SAI controller (playback)" + depends on RT_USING_DMA && RT_USING_CLK && RT_USING_RESET + default y if RT_CLK_ROCKCHIP_RK3528 + help + RK3528 SAI2/SAI3 playback for the internal DAC and HDMI. + Supports stereo S16_LE at 44.1/48 kHz using DMA. + Enable the board's DMA controller driver as well (PL330 on RK3528). + menuconfig RT_AUDIO_ROCKCHIP_CODEC bool "Rockchip codec support" - depends on RT_AUDIO_ROCKCHIP_I2S_TDM + depends on RT_AUDIO_ROCKCHIP_I2S_TDM || RT_AUDIO_ROCKCHIP_SAI default y help Codec drivers for analog audio (headphone / line-out). @@ -29,17 +38,25 @@ if RT_AUDIO_ROCKCHIP_CODEC config RT_AUDIO_ROCKCHIP_CODEC_RK817 bool "RK817/RK809 codec (I2C PMIC)" - depends on RT_USING_I2C + depends on RT_USING_I2C && RT_AUDIO_ROCKCHIP_I2S_TDM select RT_MFD_RK8XX_I2C default y help RK809/RK817 built-in codec on ROCK 3A, routed over I2S1. +config RT_AUDIO_ROCKCHIP_CODEC_RK3528 + bool "RK3528 internal DAC codec" + depends on RT_AUDIO_ROCKCHIP_SAI && RT_USING_PIN + default y if RT_CLK_ROCKCHIP_RK3528 + help + Stereo line-out playback through the RK3528 internal DAC, + including optional pa-ctl GPIO, volume and mute controls. + endif config RT_AUDIO_ROCKCHIP_CARD bool "Rockchip simple-audio-card (machine driver)" - depends on RT_AUDIO_ROCKCHIP_I2S_TDM + depends on RT_AUDIO_ROCKCHIP_I2S_TDM || RT_AUDIO_ROCKCHIP_SAI default y help Parses simple-audio-card nodes (e.g. rk809-sound, hdmi_sound) diff --git a/bsp/rockchip/dm/audio/SConscript b/bsp/rockchip/dm/audio/SConscript index 9d0f9686892c..a017f1fbf518 100755 --- a/bsp/rockchip/dm/audio/SConscript +++ b/bsp/rockchip/dm/audio/SConscript @@ -4,13 +4,22 @@ cwd = GetCurrentDir() src = [] CPPPATH = [cwd + '/../include'] +if GetDepend(['RT_AUDIO_ROCKCHIP_I2S_TDM']) or GetDepend(['RT_AUDIO_ROCKCHIP_SAI']): + src += ['rockchip-audio-clock.c'] + if GetDepend(['RT_AUDIO_ROCKCHIP_I2S_TDM']): src += ['rockchip-i2s-tdm.c'] +if GetDepend(['RT_AUDIO_ROCKCHIP_SAI']): + src += ['rockchip-sai.c'] + if GetDepend(['RT_AUDIO_ROCKCHIP_CODEC_RK817']): src += ['codec/rk817-codec.c'] -if GetDepend(['RT_AUDIO_ROCKCHIP_CARD', 'RT_AUDIO_ROCKCHIP_I2S_TDM']): +if GetDepend(['RT_AUDIO_ROCKCHIP_CODEC_RK3528']): + src += ['codec/rk3528-codec.c'] + +if GetDepend(['RT_AUDIO_ROCKCHIP_CARD']): src += ['rockchip-audio-card.c'] group = DefineGroup('DeviceDrivers', src, depend = [''], CPPPATH = CPPPATH) diff --git a/bsp/rockchip/dm/audio/codec/rk3528-codec.c b/bsp/rockchip/dm/audio/codec/rk3528-codec.c new file mode 100644 index 000000000000..f085039795ee --- /dev/null +++ b/bsp/rockchip/dm/audio/codec/rk3528-codec.c @@ -0,0 +1,389 @@ +/* + * Copyright (c) 2006-2026, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2026-3-15 GuEe-GUI the first version + */ + +#include + +#define DBG_TAG "audio.rk3528" +#define DBG_LVL DBG_INFO +#include + +#include "rk3528-codec.h" + +#define DAC_RESET 0x00 +#define DAC_FORMAT 0x04 +#define DAC_I2S 0x08 +#define DAC_DIG_GAIN 0x18 +#define DAC_BIAS 0x80 +#define DAC_VREF_CURRENT 0x84 +#define DAC_LEFT 0xa0 +#define DAC_RIGHT 0xb0 +#define LINEOUT_LEFT 0xa4 +#define LINEOUT_RIGHT 0xb4 +#define LINEOUT_GAIN_LEFT 0xac +#define LINEOUT_GAIN_RIGHT 0xbc + +#define DAC_SYS_RESET_N RT_BIT(0) +#define DAC_RESET_N RT_BIT(4) +#define DAC_I2S_RESET_N RT_BIT(0) +#define DAC_I2S_MASTER (RT_BIT(5) | RT_BIT(4)) +#define DAC_I2S_FORMAT_MASK (3U << 2) +#define DAC_I2S_FORMAT (2U << 2) +#define DAC_WORD_MASK (3U << 4) +#define DAC_VREF RT_BIT(0) +#define DAC_IBIAS RT_BIT(1) +#define DAC_ENABLE RT_BIT(0) +#define DAC_CLK RT_BIT(1) +#define DAC_BUFFER RT_BIT(2) +#define DAC_CHANNEL_VREF RT_BIT(3) +#define DAC_WORK RT_BIT(4) +#define DAC_POP_MASK (3U << 5) +#define DAC_POP_ON RT_BIT(5) +#define LINEOUT_ENABLE RT_BIT(0) +#define LINEOUT_WORK RT_BIT(4) +#define LINEOUT_UNMUTE RT_BIT(5) +#define LINEOUT_GAIN_0DB 0x1a + +struct rk3528_codec +{ + rt_list_t list; + struct rt_ofw_node *np; + void *regs; + struct rt_clk *pclk; + struct rt_clk *mclk; + struct rt_reset_control *reset; + rt_base_t pa_pin; + rt_uint8_t pa_active; + rt_uint32_t pa_delay_ms; + rt_uint32_t volume; + rt_bool_t configured; + rt_bool_t started; + rt_bool_t muted; +}; + +static rt_list_t codec_list = RT_LIST_OBJECT_INIT(codec_list); +static RT_DEFINE_SPINLOCK(codec_lock); + +static void codec_update(struct rk3528_codec *codec, rt_uint32_t reg, + rt_uint32_t mask, rt_uint32_t value) +{ + HWREG32(codec->regs + reg) = (HWREG32(codec->regs + reg) & ~mask) | (value & mask); +} + +static void codec_pair(struct rk3528_codec *codec, rt_uint32_t left, + rt_uint32_t mask, rt_uint32_t value) +{ + codec_update(codec, left, mask, value); + codec_update(codec, left + 0x10, mask, value); +} + +static void codec_pa(struct rk3528_codec *codec, rt_bool_t on) +{ + if (codec->pa_pin >= 0) + { + rt_pin_write(codec->pa_pin, on ? codec->pa_active : !codec->pa_active); + if (on && codec->pa_delay_ms) + { + rt_thread_mdelay(codec->pa_delay_ms); + } + } +} + +static void codec_apply_mute(struct rk3528_codec *codec) +{ + rt_bool_t audible = codec->started && !codec->muted && codec->volume; + + if (!audible) + { + codec_pa(codec, RT_FALSE); + } + codec_pair(codec, LINEOUT_LEFT, LINEOUT_UNMUTE, audible ? LINEOUT_UNMUTE : 0); + if (audible) + { + codec_pa(codec, RT_TRUE); + } +} + +struct rk3528_codec *rk3528_codec_find(struct rt_ofw_node *np) +{ + struct rk3528_codec *codec; + struct rk3528_codec *found; + + found = RT_NULL; + + rt_spin_lock(&codec_lock); + rt_list_for_each_entry(codec, &codec_list, list) + { + if (codec->np == np) + { + found = codec; + break; + } + } + rt_spin_unlock(&codec_lock); + return found; +} + +rt_err_t rk3528_codec_hw_params(struct rk3528_codec *codec, + rt_uint32_t rate, rt_uint16_t bits, rt_uint32_t mclk_fs) +{ + rt_err_t err; + rt_uint32_t mclk; + rt_uint64_t actual; + rt_uint64_t delta; + + if (bits != 16 || (rate != 44100 && rate != 48000) || mclk_fs != 256) + { + return -RT_EINVAL; + } + if (codec->started) + { + return -RT_EBUSY; + } + codec->configured = RT_FALSE; + mclk = rate * mclk_fs; + if ((err = rt_clk_set_rate(codec->mclk, mclk))) + { + return err; + } + actual = rt_clk_get_rate(codec->mclk); + delta = actual > mclk ? actual - mclk : mclk - actual; + if (delta > mclk / 100000) + { + return -RT_EINVAL; + } + codec_update(codec, DAC_FORMAT, DAC_WORD_MASK | DAC_I2S_FORMAT_MASK, + DAC_I2S_FORMAT); + codec_update(codec, DAC_I2S, DAC_I2S_MASTER | DAC_I2S_RESET_N, + DAC_I2S_RESET_N); + codec->configured = RT_TRUE; + return RT_EOK; +} + +rt_err_t rk3528_codec_set_volume(struct rk3528_codec *codec, rt_uint32_t volume) +{ + if (volume > AUDIO_VOLUME_MAX) + { + return -RT_EINVAL; + } + codec->volume = volume; + codec_pair(codec, LINEOUT_GAIN_LEFT, 0x1f, + volume * LINEOUT_GAIN_0DB / AUDIO_VOLUME_MAX); + codec_apply_mute(codec); + return RT_EOK; +} + +rt_err_t rk3528_codec_mute(struct rk3528_codec *codec, rt_bool_t mute) +{ + codec->muted = mute; + codec_apply_mute(codec); + return RT_EOK; +} + +rt_err_t rk3528_codec_start(struct rk3528_codec *codec) +{ + if (!codec->configured) + { + return -RT_EINVAL; + } + if (codec->started) + { + return RT_EOK; + } + + /* Enable both channels in the vendor analog power-up order, still muted. */ + codec_update(codec, DAC_BIAS, DAC_IBIAS, DAC_IBIAS); + codec_pair(codec, DAC_LEFT, DAC_BUFFER, DAC_BUFFER); + codec_pair(codec, DAC_LEFT, DAC_POP_MASK, DAC_POP_ON); + codec_pair(codec, LINEOUT_LEFT, LINEOUT_ENABLE, LINEOUT_ENABLE); + codec_pair(codec, LINEOUT_LEFT, LINEOUT_WORK, LINEOUT_WORK); + codec_pair(codec, DAC_LEFT, DAC_CHANNEL_VREF, DAC_CHANNEL_VREF); + codec_pair(codec, DAC_LEFT, DAC_CLK, DAC_CLK); + codec_pair(codec, DAC_LEFT, DAC_ENABLE, DAC_ENABLE); + codec_pair(codec, DAC_LEFT, DAC_WORK, DAC_WORK); + codec->started = RT_TRUE; + codec_apply_mute(codec); + return RT_EOK; +} + +rt_err_t rk3528_codec_stop(struct rk3528_codec *codec) +{ + codec->started = RT_FALSE; + codec_apply_mute(codec); + codec_pair(codec, LINEOUT_LEFT, LINEOUT_WORK, 0); + codec_pair(codec, LINEOUT_LEFT, LINEOUT_ENABLE, 0); + codec_pair(codec, DAC_LEFT, DAC_ENABLE, 0); + codec_pair(codec, DAC_LEFT, DAC_CLK, 0); + codec_pair(codec, DAC_LEFT, DAC_CHANNEL_VREF, 0); + codec_pair(codec, DAC_LEFT, DAC_POP_MASK, 0); + codec_pair(codec, DAC_LEFT, DAC_BUFFER, 0); + codec_update(codec, DAC_BIAS, DAC_IBIAS, 0); + codec_pair(codec, DAC_LEFT, DAC_WORK, 0); + return RT_EOK; +} + +static rt_err_t rk3528_codec_probe(struct rt_platform_device *pdev) +{ + rt_err_t err; + struct rt_device *dev = &pdev->parent; + struct rk3528_codec *codec; + + codec = rt_calloc(1, sizeof(*codec)); + + if (!codec) + { + return -RT_ENOMEM; + } + codec->np = dev->ofw_node; + codec->pa_pin = PIN_NONE; + if (rt_dm_dev_prop_read_bool(dev, "pa-ctl-gpios")) + { + codec->pa_pin = rt_pin_get_named_pin(dev, "pa-ctl", 0, RT_NULL, + &codec->pa_active); + if (codec->pa_pin < 0) + { + err = codec->pa_pin; + goto _free; + } + rt_pin_mode(codec->pa_pin, PIN_MODE_OUTPUT); + codec_pa(codec, RT_FALSE); + } + rt_dm_dev_prop_read_u32(dev, "pa-ctl-delay-ms", &codec->pa_delay_ms); + if (codec->pa_delay_ms > 1000) + { + err = -RT_EINVAL; + goto _free; + } + codec->regs = rt_dm_dev_iomap(dev, 0); + if (!codec->regs) + { + err = -RT_EIO; + goto _free; + } + codec->pclk = rt_clk_get_by_name(dev, "pclk"); + codec->mclk = rt_clk_get_by_name(dev, "mclk"); + codec->reset = rt_reset_control_get_by_name(dev, "acodec"); + if (rt_is_err(codec->pclk) || rt_is_err(codec->mclk) || + rt_is_err(codec->reset)) + { + err = -RT_EIO; + LOG_E("Missing codec clock or reset"); + goto _put; + } + if ((err = rt_clk_prepare_enable(codec->pclk))) + { + goto _put; + } + if ((err = rt_clk_prepare_enable(codec->mclk))) + { + goto _disable_pclk; + } + if ((err = rt_reset_control_assert(codec->reset))) + { + goto _disable_mclk; + } + rt_thread_mdelay(10); + if ((err = rt_reset_control_deassert(codec->reset))) + { + goto _disable_mclk; + } + codec_update(codec, DAC_RESET, DAC_RESET_N | DAC_SYS_RESET_N, 0); + codec_update(codec, DAC_I2S, DAC_I2S_RESET_N, 0); + rt_thread_mdelay(10); + codec_update(codec, DAC_RESET, DAC_RESET_N | DAC_SYS_RESET_N, + DAC_RESET_N | DAC_SYS_RESET_N); + codec_update(codec, DAC_I2S, DAC_I2S_RESET_N, DAC_I2S_RESET_N); + + codec_pair(codec, DAC_LEFT, DAC_POP_MASK, DAC_POP_ON); + codec_update(codec, DAC_VREF_CURRENT, 0xff, 0xff); + codec_update(codec, DAC_BIAS, DAC_VREF, DAC_VREF); + rt_thread_mdelay(20); + codec_update(codec, DAC_VREF_CURRENT, 0xff, 2); + rk3528_codec_stop(codec); + codec_update(codec, DAC_DIG_GAIN, 0xff, 0xe1); + rk3528_codec_set_volume(codec, AUDIO_VOLUME_MAX); + + rt_spin_lock(&codec_lock); + rt_list_insert_before(&codec_list, &codec->list); + rt_spin_unlock(&codec_lock); + dev->user_data = codec; + rt_dm_dev_bind_fwdata(dev, RT_NULL, codec); + LOG_D("%s: internal DAC ready", codec->np->name); + return RT_EOK; + +_disable_mclk: + rt_clk_disable_unprepare(codec->mclk); + +_disable_pclk: + rt_clk_disable_unprepare(codec->pclk); + +_put: + if (!rt_is_err_or_null(codec->reset)) + { + rt_reset_control_put(codec->reset); + } + if (!rt_is_err_or_null(codec->mclk)) + { + rt_clk_put(codec->mclk); + } + if (!rt_is_err_or_null(codec->pclk)) + { + rt_clk_put(codec->pclk); + } + rt_iounmap(codec->regs); + +_free: + rt_free(codec); + + return err; +} + +static rt_err_t rk3528_codec_remove(struct rt_platform_device *pdev) +{ + struct rk3528_codec *codec = pdev->parent.user_data; + + rk3528_codec_stop(codec); + codec_update(codec, DAC_VREF_CURRENT, 0xff, 0xff); + codec_update(codec, DAC_BIAS, DAC_VREF, 0); + rt_thread_mdelay(20); + + rt_dm_dev_unbind_fwdata(&pdev->parent, RT_NULL); + + rt_spin_lock(&codec_lock); + rt_list_remove(&codec->list); + rt_spin_unlock(&codec_lock); + + rt_reset_control_assert(codec->reset); + rt_reset_control_put(codec->reset); + + rt_clk_disable_unprepare(codec->mclk); + rt_clk_disable_unprepare(codec->pclk); + rt_clk_put(codec->mclk); + rt_clk_put(codec->pclk); + + rt_iounmap(codec->regs); + rt_free(codec); + + return RT_EOK; +} + +static const struct rt_ofw_node_id rk3528_codec_ids[] = +{ + { .compatible = "rockchip,rk3528-codec" }, + { /* sentinel */ } +}; + +static struct rt_platform_driver rk3528_codec_driver = +{ + .name = "rk3528-codec", + .ids = rk3528_codec_ids, + .probe = rk3528_codec_probe, + .remove = rk3528_codec_remove, +}; +RT_PLATFORM_DRIVER_EXPORT(rk3528_codec_driver); diff --git a/bsp/rockchip/dm/audio/codec/rk3528-codec.h b/bsp/rockchip/dm/audio/codec/rk3528-codec.h new file mode 100644 index 000000000000..f50434f8e3d7 --- /dev/null +++ b/bsp/rockchip/dm/audio/codec/rk3528-codec.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2006-2026, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2026-3-15 GuEe-GUI the first version + */ + +#ifndef __RK3528_CODEC_H__ +#define __RK3528_CODEC_H__ + +#include + +struct rk3528_codec; + +#ifdef RT_AUDIO_ROCKCHIP_CODEC_RK3528 +struct rk3528_codec *rk3528_codec_find(struct rt_ofw_node *np); +rt_err_t rk3528_codec_hw_params(struct rk3528_codec *codec, + rt_uint32_t rate, rt_uint16_t bits, + rt_uint32_t mclk_fs); +rt_err_t rk3528_codec_start(struct rk3528_codec *codec); +rt_err_t rk3528_codec_stop(struct rk3528_codec *codec); +rt_err_t rk3528_codec_set_volume(struct rk3528_codec *codec, + rt_uint32_t volume); +rt_err_t rk3528_codec_mute(struct rk3528_codec *codec, rt_bool_t mute); +#else +rt_inline struct rk3528_codec *rk3528_codec_find(struct rt_ofw_node *np) +{ + return RT_NULL; +} +rt_inline rt_err_t rk3528_codec_hw_params(struct rk3528_codec *codec, + rt_uint32_t rate, rt_uint16_t bits, + rt_uint32_t mclk_fs) +{ + return -RT_ENOSYS; +} +rt_inline rt_err_t rk3528_codec_start(struct rk3528_codec *codec) +{ + return -RT_ENOSYS; +} +rt_inline rt_err_t rk3528_codec_stop(struct rk3528_codec *codec) +{ + return -RT_ENOSYS; +} +rt_inline rt_err_t rk3528_codec_set_volume(struct rk3528_codec *codec, + rt_uint32_t volume) +{ + return -RT_ENOSYS; +} +rt_inline rt_err_t rk3528_codec_mute(struct rk3528_codec *codec, rt_bool_t mute) +{ + return -RT_ENOSYS; +} +#endif + +#endif diff --git a/bsp/rockchip/dm/audio/codec/rk817-codec.h b/bsp/rockchip/dm/audio/codec/rk817-codec.h index 33be4d1c913e..1c88c9293fe4 100644 --- a/bsp/rockchip/dm/audio/codec/rk817-codec.h +++ b/bsp/rockchip/dm/audio/codec/rk817-codec.h @@ -72,6 +72,7 @@ struct rk817_codec_config struct rk817_codec; +#ifdef RT_AUDIO_ROCKCHIP_CODEC_RK817 rt_err_t rk817_codec_bind_rk8xx(struct rk817_codec **out_codec, struct rk8xx *rk8xx); rt_err_t rk817_codec_hw_params(struct rk817_codec *codec, const struct rk817_codec_config *cfg); rt_err_t rk817_codec_start(struct rk817_codec *codec); @@ -79,5 +80,35 @@ rt_err_t rk817_codec_stop(struct rk817_codec *codec); rt_err_t rk817_codec_set_volume(struct rk817_codec *codec, rt_uint8_t volume); struct rk817_codec *rk817_codec_find(struct rt_ofw_node *np); struct rk8xx *rk817_codec_rk8xx_from_np(struct rt_ofw_node *np); +#else +rt_inline rt_err_t rk817_codec_bind_rk8xx(struct rk817_codec **out_codec, struct rk8xx *rk8xx) +{ + return -RT_ENOSYS; +} +rt_inline rt_err_t rk817_codec_hw_params(struct rk817_codec *codec, const struct rk817_codec_config *cfg) +{ + return -RT_ENOSYS; +} +rt_inline rt_err_t rk817_codec_start(struct rk817_codec *codec) +{ + return -RT_ENOSYS; +} +rt_inline rt_err_t rk817_codec_stop(struct rk817_codec *codec) +{ + return -RT_ENOSYS; +} +rt_inline rt_err_t rk817_codec_set_volume(struct rk817_codec *codec, rt_uint8_t volume) +{ + return -RT_ENOSYS; +} +rt_inline struct rk817_codec *rk817_codec_find(struct rt_ofw_node *np) +{ + return RT_NULL; +} +rt_inline struct rk8xx *rk817_codec_rk8xx_from_np(struct rt_ofw_node *np) +{ + return RT_NULL; +} +#endif #endif /* __RK817_CODEC_H__ */ diff --git a/bsp/rockchip/dm/audio/rockchip-audio-card.c b/bsp/rockchip/dm/audio/rockchip-audio-card.c index 6b9ec4ba87fe..661e373a21cd 100644 --- a/bsp/rockchip/dm/audio/rockchip-audio-card.c +++ b/bsp/rockchip/dm/audio/rockchip-audio-card.c @@ -17,11 +17,14 @@ #include #include "rockchip-i2s-tdm.h" +#include "rockchip-sai.h" #include "rockchip-hdmi.h" #include "codec/rk817-codec.h" +#include "codec/rk3528-codec.h" #define CARD_PREFIX "simple-audio-card," #define CARD_DAI "sound-dai" +#define ROCKCHIP_HDMI_CARD "rockchip,hdmi" #define DEFAULT_MCLK_FS 256 #ifndef RT_AUDIO_REPLAY_MP_BLOCK_SIZE @@ -44,13 +47,21 @@ struct rockchip_audio_card struct rt_device *dev; struct rockchip_i2s_tdm *i2s; + struct rockchip_sai *sai; struct rk817_codec *codec; + struct rk3528_codec *acodec; struct rt_ofw_node *hdmi_np; rt_uint32_t mclk_fs; + struct rt_spinlock replay_lock; + struct rt_completion replay_idle; + rt_uint32_t replay_callbacks; rt_bool_t started; rt_bool_t is_hdmi; rt_bool_t use_dma; + rt_bool_t playback_coherent; + rt_uint32_t volume; + rt_bool_t muted; char name[32]; struct rt_dma_chan *dma_tx; @@ -134,7 +145,10 @@ static rt_err_t rockchip_audio_card_getcaps(struct rt_audio_device *audio, caps->udata.mask = AUDIO_MIXER_VOLUME | AUDIO_MIXER_MUTE; break; case AUDIO_MIXER_VOLUME: - caps->udata.value = AUDIO_VOLUME_MAX; + caps->udata.value = card->acodec ? card->volume : AUDIO_VOLUME_MAX; + break; + case AUDIO_MIXER_MUTE: + caps->udata.value = card->muted; break; default: break; @@ -155,6 +169,7 @@ static rt_err_t rockchip_audio_card_configure(struct rt_audio_device *audio, struct rockchip_audio_card *card = audio->parent.user_data; struct rockchip_i2s_config i2s_cfg; struct rk817_codec_config codec_cfg; + struct rt_audio_configure config; switch (caps->main_type) { @@ -162,8 +177,26 @@ static rt_err_t rockchip_audio_card_configure(struct rt_audio_device *audio, switch (caps->sub_type) { case AUDIO_MIXER_MUTE: + if (card->acodec) + { + err = rk3528_codec_mute(card->acodec, !!caps->udata.value); + if (err) + { + return err; + } + card->muted = !!caps->udata.value; + } break; case AUDIO_MIXER_VOLUME: + if (card->acodec) + { + err = rk3528_codec_set_volume(card->acodec, caps->udata.value); + if (err) + { + return err; + } + card->volume = caps->udata.value; + } if (!card->is_hdmi && card->codec) { rk817_codec_set_volume(card->codec, caps->udata.value); @@ -178,32 +211,58 @@ static rt_err_t rockchip_audio_card_configure(struct rt_audio_device *audio, switch (caps->sub_type) { case AUDIO_DSP_PARAM: - card->config = caps->udata.config; + if (card->sai && card->started) + { + return -RT_EBUSY; + } + config = caps->udata.config; - if (!card->config.samplerate) + if (!config.samplerate) { - card->config.samplerate = 48000; + config.samplerate = 48000; } - if (!card->config.channels) + if (!config.channels) { - card->config.channels = 2; + config.channels = 2; } - if (!card->config.samplebits) + if (!config.samplebits) { - card->config.samplebits = 16; + config.samplebits = 16; } - i2s_cfg.samplerate = card->config.samplerate; - i2s_cfg.channels = card->config.channels; - i2s_cfg.samplebits = card->config.samplebits; + i2s_cfg.samplerate = config.samplerate; + i2s_cfg.channels = config.channels; + i2s_cfg.samplebits = config.samplebits; i2s_cfg.mclk_fs = card->mclk_fs; - codec_cfg.samplerate = card->config.samplerate; - codec_cfg.samplebits = card->config.samplebits; - codec_cfg.mclk_rate = card->config.samplerate * card->mclk_fs; + codec_cfg.samplerate = config.samplerate; + codec_cfg.samplebits = config.samplebits; + codec_cfg.mclk_rate = config.samplerate * card->mclk_fs; + + if (card->sai) + { + if (config.channels != 2 || config.samplebits != 16 || + (config.samplerate != 44100 && config.samplerate != 48000)) + { + return -RT_EINVAL; + } + if ((err = rockchip_sai_hw_params(card->sai, &i2s_cfg))) + { + return err; + } + /* SAI2 establishes the shared source before the DAC divider. */ + if (card->acodec && (err = rk3528_codec_hw_params(card->acodec, + config.samplerate, config.samplebits, card->mclk_fs))) + { + return err; + } + card->config = config; + return RT_EOK; + } + card->config = config; if (!card->is_hdmi && card->codec) { if ((err = rk817_codec_hw_params(card->codec, &codec_cfg))) @@ -234,6 +293,32 @@ static rt_err_t rockchip_audio_card_init(struct rt_audio_device *audio) return RT_EOK; } +static void rockchip_audio_card_queue_frame(struct rockchip_audio_card *card) +{ + rt_base_t level = rt_spin_lock_irqsave(&card->replay_lock); + + if (!card->started) + { + rt_spin_unlock_irqrestore(&card->replay_lock, level); + return; + } + ++card->replay_callbacks; + rt_spin_unlock_irqrestore(&card->replay_lock, level); + + if (card->i2s) + { + rockchip_i2s_tdm_dma_check_underrun(card->i2s); + } + rt_audio_tx_complete(&card->audio); + + level = rt_spin_lock_irqsave(&card->replay_lock); + if (!--card->replay_callbacks && !card->started) + { + rt_completion_done(&card->replay_idle); + } + rt_spin_unlock_irqrestore(&card->replay_lock, level); +} + static rt_err_t rockchip_audio_card_start(struct rt_audio_device *audio, int stream) { rt_err_t err; @@ -246,18 +331,32 @@ static rt_err_t rockchip_audio_card_start(struct rt_audio_device *audio, int str if (card->is_hdmi) { + rt_bool_t qp = rt_ofw_node_is_compatible(card->hdmi_np, "rockchip,rk3588-dw-hdmi"); + + /* QP enable prepares its controller; legacy DW requires hw_params first. */ + if (qp && (err = rk_hdmi_audio_enable(card->hdmi_np, RT_TRUE))) + { + return err; + } + err = rk_hdmi_audio_configure(card->hdmi_np, card->config.samplerate, card->config.channels, card->config.samplebits); if (err) { LOG_E("HDMI audio configure failed: %s", rt_strerror(err)); + rk_hdmi_audio_enable(card->hdmi_np, RT_FALSE); return err; } - - if ((err = rk_hdmi_audio_enable(card->hdmi_np, RT_TRUE))) + if (!qp && (err = rk_hdmi_audio_enable(card->hdmi_np, RT_TRUE))) + { + return err; + } + } + else if (card->acodec) + { + if ((err = rk3528_codec_start(card->acodec))) { - LOG_E("HDMI audio enable failed: %s", rt_strerror(err)); return err; } } @@ -266,39 +365,69 @@ static rt_err_t rockchip_audio_card_start(struct rt_audio_device *audio, int str rk817_codec_start(card->codec); } - if (card->i2s) + if (card->sai) + { + if ((err = rockchip_sai_start(card->sai))) + { + goto _stop_i2s; + } + } + else if (card->i2s) { if ((err = rockchip_i2s_tdm_start(card->i2s))) { - if (card->is_hdmi) - { - rk_hdmi_audio_enable(card->hdmi_np, RT_FALSE); - } - return err; + goto _stop_i2s; } - /* TX reset in start() clears DMACR, so enable requests afterwards. */ if (card->use_dma && (err = rockchip_i2s_tdm_dma_enable(card->i2s))) { - rockchip_i2s_tdm_stop(card->i2s); - if (card->is_hdmi) - { - rk_hdmi_audio_enable(card->hdmi_np, RT_FALSE); - } - return err; + goto _stop_i2s; } } card->started = RT_TRUE; + LOG_D("%s replay via %s", card->name, card->use_dma ? "DMA" : "PIO"); /* Prime the first replay block; later blocks are queued by DMA callbacks. */ - rt_audio_tx_complete(audio); + rockchip_audio_card_queue_frame(card); return RT_EOK; + +_stop_i2s: + LOG_E("%s: audio start failed: %s", card->name, rt_strerror(err)); + + if (card->sai) + { + rockchip_sai_stop(card->sai); + } + if (card->i2s && card->use_dma && card->dma_tx) + { + rockchip_i2s_tdm_dma_disable(card->i2s); + } + if (card->i2s) + { + rockchip_i2s_tdm_stop(card->i2s); + } + if (card->acodec) + { + rk3528_codec_stop(card->acodec); + } + if (card->codec) + { + rk817_codec_stop(card->codec); + } + if (card->is_hdmi) + { + rk_hdmi_audio_enable(card->hdmi_np, RT_FALSE); + } + return err; } static rt_err_t rockchip_audio_card_stop(struct rt_audio_device *audio, int stream) { + rt_err_t err, result = RT_EOK; + rt_base_t level; + rt_bool_t busy; struct rockchip_audio_card *card = audio->parent.user_data; if (stream != AUDIO_STREAM_REPLAY) @@ -306,9 +435,25 @@ static rt_err_t rockchip_audio_card_stop(struct rt_audio_device *audio, int stre return -RT_EINVAL; } + /* The core can wake close() before its callback submits the final silence. */ + level = rt_spin_lock_irqsave(&card->replay_lock); + card->started = RT_FALSE; + busy = card->replay_callbacks != 0; + if (busy) + { + rt_completion_init(&card->replay_idle); + } + rt_spin_unlock_irqrestore(&card->replay_lock, level); + if (busy && (result = rt_completion_wait(&card->replay_idle, + rt_tick_from_millisecond(1000)))) + { + LOG_E("%s: replay callback did not finish", card->name); + return result; + } + if (card->use_dma && card->dma_tx) { - rt_dma_chan_stop(card->dma_tx); + result = rt_dma_chan_stop(card->dma_tx); } if (card->i2s) @@ -320,6 +465,18 @@ static rt_err_t rockchip_audio_card_stop(struct rt_audio_device *audio, int stre rockchip_i2s_tdm_stop(card->i2s); } + if (card->sai) + { + err = rockchip_sai_stop(card->sai); + if (!result) + { + result = err; + } + } + if (card->acodec) + { + rk3528_codec_stop(card->acodec); + } if (!card->is_hdmi && card->codec) { @@ -330,9 +487,13 @@ static rt_err_t rockchip_audio_card_stop(struct rt_audio_device *audio, int stre rk_hdmi_audio_enable(card->hdmi_np, RT_FALSE); } - card->started = RT_FALSE; + if (card->i2s && card->use_dma && rockchip_i2s_tdm_dma_underruns(card->i2s)) + { + LOG_W("%s: TX FIFO underruns: %u", card->name, + rockchip_i2s_tdm_dma_underruns(card->i2s)); + } - return RT_EOK; + return result; } static void rockchip_audio_card_dma_tx_done(struct rt_dma_chan *chan, rt_size_t size) @@ -341,7 +502,7 @@ static void rockchip_audio_card_dma_tx_done(struct rt_dma_chan *chan, rt_size_t RT_UNUSED(size); - rt_audio_tx_complete(&card->audio); + rockchip_audio_card_queue_frame(card); } static rt_ssize_t rockchip_audio_card_dma_transmit(struct rockchip_audio_card *card, @@ -377,9 +538,6 @@ static rt_ssize_t rockchip_audio_card_dma_transmit(struct rockchip_audio_card *c return -RT_EINVAL; } - rt_dma_chan_stop(card->dma_tx); - - /* The audio core submits one block at a time. */ if ((err = rt_dma_prep_single(card->dma_tx, &transfer))) { LOG_E("DMA prep failed: %s", rt_strerror(err)); @@ -402,7 +560,7 @@ static rt_ssize_t rockchip_audio_card_transmit(struct rt_audio_device *audio, RT_UNUSED(readBuf); - if (!card->started || !card->i2s) + if (!card->started || (!card->i2s && !card->sai)) { return 0; } @@ -420,19 +578,11 @@ static void rockchip_audio_card_buffer_info(struct rt_audio_device *audio, { struct rockchip_audio_card *card = audio->parent.user_data; - if (card->use_dma && card->playback_buf) - { - info->buffer = card->playback_buf; - info->block_size = RT_AUDIO_REPLAY_MP_BLOCK_SIZE; - info->block_count = RT_AUDIO_REPLAY_MP_BLOCK_COUNT; - info->total_size = ROCKCHIP_AUDIO_PLAYBACK_SIZE; - return; - } - - info->buffer = RT_NULL; + RT_ASSERT(card->playback_buf); + info->buffer = card->playback_buf; info->block_size = RT_AUDIO_REPLAY_MP_BLOCK_SIZE; info->block_count = RT_AUDIO_REPLAY_MP_BLOCK_COUNT; - info->total_size = info->block_size * info->block_count; + info->total_size = ROCKCHIP_AUDIO_PLAYBACK_SIZE; } static const struct rt_audio_ops rockchip_audio_card_ops = @@ -481,6 +631,7 @@ static rt_err_t rockchip_audio_card_probe(struct rt_platform_device *pdev) struct rt_ofw_node *cpu_np, *codec_np; struct rockchip_audio_card *card; const char *card_name = "rockchip-audio"; + rt_bool_t vendor_hdmi; card = rt_calloc(1, sizeof(*card)); @@ -489,17 +640,25 @@ static rt_err_t rockchip_audio_card_probe(struct rt_platform_device *pdev) return -RT_ENOMEM; } - rt_ofw_prop_read_string(card_np, CARD_PREFIX "name", &card_name); + rt_spin_lock_init(&card->replay_lock); + rt_completion_init(&card->replay_idle); + + vendor_hdmi = rt_ofw_node_is_compatible(card_np, ROCKCHIP_HDMI_CARD); + rt_ofw_prop_read_string(card_np, vendor_hdmi ? "rockchip,card-name" : + CARD_PREFIX "name", &card_name); rt_strncpy(card->name, card_name, sizeof(card->name) - 1); - rt_ofw_prop_read_u32(card_np, CARD_PREFIX "mclk-fs", &card->mclk_fs); + rt_ofw_prop_read_u32(card_np, vendor_hdmi ? "rockchip,mclk-fs" : + CARD_PREFIX "mclk-fs", &card->mclk_fs); if (!card->mclk_fs) { card->mclk_fs = DEFAULT_MCLK_FS; } - cpu_np = rockchip_audio_parse_dai_np(card_np, CARD_PREFIX "cpu"); - codec_np = rockchip_audio_parse_dai_np(card_np, CARD_PREFIX "codec"); + cpu_np = vendor_hdmi ? rt_ofw_parse_phandle(card_np, "rockchip,cpu", 0) : + rockchip_audio_parse_dai_np(card_np, CARD_PREFIX "cpu"); + codec_np = vendor_hdmi ? rt_ofw_parse_phandle(card_np, "rockchip,codec", 0) : + rockchip_audio_parse_dai_np(card_np, CARD_PREFIX "codec"); if (!cpu_np) { @@ -509,10 +668,11 @@ static rt_err_t rockchip_audio_card_probe(struct rt_platform_device *pdev) } card->i2s = rockchip_i2s_tdm_find(cpu_np); + card->sai = rockchip_sai_find(cpu_np); - if (!card->i2s) + if (!card->i2s && !card->sai) { - LOG_E("I2S not ready for %s", card->name); + LOG_E("CPU DAI %s not ready for %s", cpu_np->name, card->name); err = -RT_ENOSYS; goto _fail; } @@ -520,12 +680,23 @@ static rt_err_t rockchip_audio_card_probe(struct rt_platform_device *pdev) if (codec_np) { if (rt_ofw_node_is_compatible(codec_np, "rockchip,rk3568-dw-hdmi") || + rt_ofw_node_is_compatible(codec_np, "rockchip,rk3528-dw-hdmi") || rt_ofw_node_is_compatible(codec_np, "rockchip,rk3588-dw-hdmi")) { card->is_hdmi = RT_TRUE; card->hdmi_np = codec_np; rt_ofw_node_get(card->hdmi_np); } + else if (rt_ofw_node_is_compatible(codec_np, "rockchip,rk3528-codec")) + { + card->acodec = rk3528_codec_find(codec_np); + if (!card->acodec) + { + LOG_E("RK3528 codec not ready for %s", card->name); + err = -RT_ENOSYS; + goto _fail; + } + } else { card->codec = rockchip_audio_card_get_codec(codec_np); @@ -540,9 +711,12 @@ static rt_err_t rockchip_audio_card_probe(struct rt_platform_device *pdev) card->config.samplerate = 48000; card->config.channels = 2; card->config.samplebits = 16; + card->volume = AUDIO_VOLUME_MAX; card->dev = dev; - card->dma_tx = rockchip_i2s_tdm_get_dma_tx(card->i2s); - card->txdr_dma = rockchip_i2s_tdm_txdr_phys(card->i2s); + card->dma_tx = card->sai ? rockchip_sai_dma_tx(card->sai) : + rockchip_i2s_tdm_get_dma_tx(card->i2s); + card->txdr_dma = card->sai ? rockchip_sai_txdr_phys(card->sai) : + rockchip_i2s_tdm_txdr_phys(card->i2s); if (card->dma_tx && card->txdr_dma) { @@ -554,9 +728,10 @@ static rt_err_t rockchip_audio_card_probe(struct rt_platform_device *pdev) card->dma_tx->callback = rockchip_audio_card_dma_tx_done; card->dma_tx->priv = card; card->use_dma = RT_TRUE; + card->playback_coherent = RT_TRUE; LOG_D("%s: DMA playback enabled", card->name); } - else + else if (!card->sai) { LOG_W("%s: DMA buffer alloc failed, using PIO", card->name); } @@ -566,6 +741,22 @@ static rt_err_t rockchip_audio_card_probe(struct rt_platform_device *pdev) LOG_W("%s: No DMA channel, using PIO", card->name); } + if (!card->playback_buf) + { + if (card->sai) + { + LOG_E("%s: SAI playback requires DMA and a coherent buffer", card->name); + err = -RT_ENOMEM; + goto _fail; + } + card->playback_buf = rt_malloc(ROCKCHIP_AUDIO_PLAYBACK_SIZE); + if (!card->playback_buf) + { + err = -RT_ENOMEM; + goto _fail; + } + } + card->audio.ops = (struct rt_audio_ops *)&rockchip_audio_card_ops; card->audio.parent.user_data = card; @@ -585,9 +776,26 @@ static rt_err_t rockchip_audio_card_probe(struct rt_platform_device *pdev) return RT_EOK; _fail: + if (card->dma_tx && card->dma_tx->priv == card) + { + card->dma_tx->callback = RT_NULL; + card->dma_tx->priv = RT_NULL; + } rt_ofw_node_put(cpu_np); rt_ofw_node_put(codec_np); rt_ofw_node_put(card->hdmi_np); + if (card->playback_buf) + { + if (card->playback_coherent) + { + rt_dma_free_coherent(card->dev, ROCKCHIP_AUDIO_PLAYBACK_SIZE, + card->playback_buf, card->playback_dma); + } + else + { + rt_free(card->playback_buf); + } + } rt_free(card); return err; @@ -602,13 +810,33 @@ static rt_err_t rockchip_audio_card_remove(struct rt_platform_device *pdev) return RT_EOK; } + if (card->started) + { + rt_err_t err = rockchip_audio_card_stop(&card->audio, AUDIO_STREAM_REPLAY); + if (err) + { + return err; + } + } + if (card->dma_tx && card->dma_tx->priv == card) + { + card->dma_tx->callback = RT_NULL; + card->dma_tx->priv = RT_NULL; + } rt_device_unregister(&card->audio.parent); rt_dm_dev_unbind_fwdata(&pdev->parent, RT_NULL); if (card->playback_buf) { - rt_dma_free_coherent(card->dev, ROCKCHIP_AUDIO_PLAYBACK_SIZE, - card->playback_buf, card->playback_dma); + if (card->playback_coherent) + { + rt_dma_free_coherent(card->dev, ROCKCHIP_AUDIO_PLAYBACK_SIZE, + card->playback_buf, card->playback_dma); + } + else + { + rt_free(card->playback_buf); + } } rt_ofw_node_put(card->hdmi_np); @@ -621,6 +849,7 @@ static rt_err_t rockchip_audio_card_remove(struct rt_platform_device *pdev) static const struct rt_ofw_node_id rockchip_audio_card_ofw_ids[] = { { .compatible = "simple-audio-card" }, + { .compatible = "rockchip,hdmi" }, { /* sentinel */ } }; diff --git a/bsp/rockchip/dm/audio/rockchip-audio-clock.c b/bsp/rockchip/dm/audio/rockchip-audio-clock.c new file mode 100644 index 000000000000..f3d82b494442 --- /dev/null +++ b/bsp/rockchip/dm/audio/rockchip-audio-clock.c @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2006-2026, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2026-3-15 GuEe-GUI the first version + */ + +#include "rockchip-audio-clock.h" + +rt_err_t rockchip_audio_set_mclk(struct rt_clk *mclk, rt_ubase_t rate) +{ + struct rt_clk_cell *cell; + struct rt_clk_cell *parent; + struct rt_clk *clk; + rt_err_t err; + rt_uint8_t index; + + if (rt_is_err_or_null(mclk)) + { + return -RT_EINVAL; + } + cell = mclk->cell; + + /* Skip pass-through gates and configure the first programmable clock. */ + while (cell->parents_nr == 1 && !cell->ops->set_rate) + { + cell = rt_clk_cell_get_parent_by_index(cell, 0); + if (!cell) + { + return -RT_ENOSYS; + } + } + + if (cell->ops->set_rate) + { + clk = rt_clk_cell_get_clk(cell, RT_NULL); + return clk ? rt_clk_set_rate(clk, rate) : -RT_ENOMEM; + } + + /* I2S and SAI muxes use an internal fractional clock for audio rates. */ + for (index = 0; index < cell->parents_nr; ++index) + { + rt_ubase_t flags; + + parent = rt_clk_cell_get_parent_by_index(cell, index); + if (!parent || !parent->ops->set_rate) + { + continue; + } + if (rt_clk_cell_round_rate(parent, rate) != rate) + { + continue; + } + clk = rt_clk_cell_get_clk(parent, RT_NULL); + if (!clk) + { + continue; + } + + /* Keep the fractional divider's high-rate source, as in the I2S path. */ + flags = parent->flags; + parent->flags &= ~RT_CLK_F_SET_RATE_PARENT; + err = rt_clk_set_rate(clk, rate); + parent->flags = flags; + if (err) + { + continue; + } + return rt_clk_cell_set_parent(cell, parent); + } + return -RT_ENOSYS; +} diff --git a/bsp/rockchip/dm/audio/rockchip-audio-clock.h b/bsp/rockchip/dm/audio/rockchip-audio-clock.h new file mode 100644 index 000000000000..8c75f92e66ae --- /dev/null +++ b/bsp/rockchip/dm/audio/rockchip-audio-clock.h @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2006-2026, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2026-3-15 GuEe-GUI the first version + */ + +#ifndef __ROCKCHIP_AUDIO_CLOCK_H__ +#define __ROCKCHIP_AUDIO_CLOCK_H__ + +#include + +rt_err_t rockchip_audio_set_mclk(struct rt_clk *mclk, rt_ubase_t rate); + +#endif /* __ROCKCHIP_AUDIO_CLOCK_H__ */ diff --git a/bsp/rockchip/dm/audio/rockchip-i2s-tdm.c b/bsp/rockchip/dm/audio/rockchip-i2s-tdm.c index a27dfef1046a..828efe6d262d 100644 --- a/bsp/rockchip/dm/audio/rockchip-i2s-tdm.c +++ b/bsp/rockchip/dm/audio/rockchip-i2s-tdm.c @@ -16,9 +16,13 @@ #include "rockchip.h" #include "rockchip-i2s-tdm.h" +#include "rockchip-audio-clock.h" -#define I2S_FIFO_DEPTH 32 -#define I2S_DEFAULT_MCLK_FS 256 +#define I2S_FIFO_DEPTH 32 +#define I2S_DEFAULT_MCLK_FS 256 +#define I2S_CLEAR_TIMEOUT_US 100 +#define I2S_PIO_WAIT_US 10 +#define I2S_PIO_TIMEOUT_US 100000 struct rockchip_i2s_tdm { @@ -37,10 +41,15 @@ struct rockchip_i2s_tdm rt_bool_t trcm_tx_only; rt_bool_t playback_only; + rt_bool_t always_on; + rt_bool_t hdmi_path; + rt_bool_t mclk_enabled; rt_bool_t running; + rt_uint32_t tx_underruns; rt_uint32_t mclk_fs; struct rockchip_i2s_config config; + rt_uint32_t pio_word_delay_us; struct rt_dma_chan *dma_tx; rt_ubase_t dma_addr; @@ -52,62 +61,6 @@ struct rockchip_i2s_tdm static rt_list_t i2s_list = RT_LIST_OBJECT_INIT(i2s_list); static RT_DEFINE_SPINLOCK(i2s_list_lock); -static rt_err_t rockchip_i2s_tdm_set_mclk(struct rockchip_i2s_tdm *i2s, - rt_ubase_t rate) -{ - struct rt_clk_cell *cell, *parent; - struct rt_clk *clk; - rt_err_t err; - - cell = i2s->mclk_tx->cell; - - /* Skip pass-through gates and configure the first programmable clock. */ - while (cell->parents_nr == 1 && !cell->ops->set_rate) - { - cell = rt_clk_cell_get_parent_by_index(cell, 0); - if (!cell) - { - return -RT_ENOSYS; - } - } - - if (cell->ops->set_rate) - { - clk = rt_clk_cell_get_clk(cell, RT_NULL); - return clk ? rt_clk_set_rate(clk, rate) : -RT_ENOMEM; - } - - /* Rockchip I2S muxes use an internal fractional clock for audio rates. */ - for (rt_uint8_t index = 0; index < cell->parents_nr; ++index) - { - parent = rt_clk_cell_get_parent_by_index(cell, index); - if (!parent || !parent->ops->set_rate) - { - continue; - } - - if (rt_clk_cell_round_rate(parent, rate) != rate) - { - continue; - } - - clk = rt_clk_cell_get_clk(parent, RT_NULL); - if (!clk || (err = rt_clk_set_rate(clk, rate))) - { - continue; - } - - if ((err = rt_clk_cell_set_parent(cell, parent))) - { - return err; - } - - return RT_EOK; - } - - return -RT_ENOSYS; -} - rt_inline rt_uint32_t i2s_readl(struct rockchip_i2s_tdm *i2s, rt_ubase_t reg) { return HWREG32(i2s->regs + reg); @@ -187,6 +140,42 @@ static rt_err_t rockchip_i2s_tdm_set_fmt(struct rockchip_i2s_tdm *i2s) return RT_EOK; } +static rt_err_t rockchip_i2s_tdm_clear_tx(struct rockchip_i2s_tdm *i2s) +{ + rt_uint32_t retry; + + i2s_writel(i2s, I2S_CLR, I2S_CLR_TXC); + + for (retry = 0; retry < I2S_CLEAR_TIMEOUT_US / 10; ++retry) + { + if (!(i2s_readl(i2s, I2S_CLR) & I2S_CLR_TXC)) + { + return RT_EOK; + } + rt_hw_us_delay(10); + } + + /* Match Linux's recovery for a TX FIFO that refuses to clear. */ + if (!rt_is_err_or_null(i2s->rst_tx)) + { + rt_reset_control_reset(i2s->rst_tx); + rt_hw_us_delay(10); + } + + i2s_writel(i2s, I2S_CLR, I2S_CLR_TXC); + for (retry = 0; retry < I2S_CLEAR_TIMEOUT_US / 10; ++retry) + { + if (!(i2s_readl(i2s, I2S_CLR) & I2S_CLR_TXC)) + { + return RT_EOK; + } + rt_hw_us_delay(10); + } + + LOG_E("TX FIFO clear timeout, clr=%08x", i2s_readl(i2s, I2S_CLR)); + return -RT_ETIMEOUT; +} + rt_err_t rockchip_i2s_tdm_hw_params(struct rockchip_i2s_tdm *i2s, const struct rockchip_i2s_config *cfg) { @@ -195,28 +184,32 @@ rt_err_t rockchip_i2s_tdm_hw_params(struct rockchip_i2s_tdm *i2s, rt_uint32_t fmt = I2S_TXCR_VDW(cfg->samplebits) | I2S_CHN_2; rt_err_t err; - if (!i2s || !cfg) + if (!i2s || !cfg || !cfg->samplerate || !cfg->channels) { return -RT_EINVAL; } i2s->config = *cfg; i2s->mclk_fs = cfg->mclk_fs ? cfg->mclk_fs : I2S_DEFAULT_MCLK_FS; + i2s->pio_word_delay_us = RT_DIV_ROUND_UP(1000000U, + cfg->samplerate * cfg->channels); if (i2s->mclk_tx) { mclk_rate = cfg->samplerate * i2s->mclk_fs; - if ((err = rockchip_i2s_tdm_set_mclk(i2s, mclk_rate))) + if ((err = rockchip_audio_set_mclk(i2s->mclk_tx, mclk_rate))) { LOG_E("set mclk_tx rate %u failed", mclk_rate); return err; } - if ((err = rt_clk_prepare_enable(i2s->mclk_tx))) + if (!i2s->mclk_enabled && (err = rt_clk_prepare_enable(i2s->mclk_tx))) { return err; } + + i2s->mclk_enabled = RT_TRUE; } mclk_rate = i2s->mclk_tx ? rt_clk_get_rate(i2s->mclk_tx) : (cfg->samplerate * i2s->mclk_fs); @@ -248,6 +241,12 @@ rt_err_t rockchip_i2s_tdm_hw_params(struct rockchip_i2s_tdm *i2s, i2s_update_bits(i2s, I2S_CKR, I2S_CKR_TSD_MASK, I2S_CKR_TSD(div_lrck)); i2s_update_bits(i2s, I2S_TXCR, I2S_TXCR_VDW_MASK | I2S_TXCR_CSR_MASK, fmt); + LOG_D("%s: %u Hz %u ch %u bit, mclk=%u bclk=%u div=%u/%u regs=%08x/%08x/%08x", + rt_dm_dev_get_name(&i2s->parent), cfg->samplerate, cfg->channels, + cfg->samplebits, mclk_rate, bclk_rate, div_bclk, div_lrck, + i2s_readl(i2s, I2S_TXCR), i2s_readl(i2s, I2S_CKR), + i2s_readl(i2s, I2S_CLKDIV)); + return RT_EOK; } @@ -258,12 +257,17 @@ rt_err_t rockchip_i2s_tdm_start(struct rockchip_i2s_tdm *i2s) return -RT_EINVAL; } - if (i2s->rst_tx) + /* + * HDMI I2S is kept clocked between streams. Match the Linux sequence + * for a new stream so stale words cannot keep the DMA request asserted. + */ + if (i2s->always_on && i2s->hdmi_path) { - rt_reset_control_assert(i2s->rst_tx); - rt_reset_control_deassert(i2s->rst_tx); + i2s_update_bits(i2s, I2S_XFER, I2S_XFER_TXS_MASK, I2S_XFER_TXS_STOP); + rt_hw_us_delay(150); } + /* CLR completes in the SCLK domain, which starts with XFER below. */ i2s_writel(i2s, I2S_CLR, I2S_CLR_TXC); if (i2s->trcm_tx_only) @@ -288,24 +292,34 @@ rt_err_t rockchip_i2s_tdm_stop(struct rockchip_i2s_tdm *i2s) return -RT_EINVAL; } - if (i2s->trcm_tx_only) - { - i2s_update_bits(i2s, I2S_XFER, I2S_XFER_TXS_MASK | I2S_XFER_RXS_MASK, - I2S_XFER_TXS_STOP | I2S_XFER_RXS_STOP); - i2s_writel(i2s, I2S_CLR, I2S_CLR_TXC); - } - else + /* + * HDMI's always-on path keeps BCLK/LRCK and the transmitter alive + * between DMA streams. This is the equivalent of Linux's + * rockchip_i2s_tdm_xfer_stop(..., false), which is intentionally a + * no-op for the ALWAYS_ON quirk. + */ + if (!i2s->always_on) { - i2s_update_bits(i2s, I2S_XFER, I2S_XFER_TXS_MASK, I2S_XFER_TXS_STOP); - i2s_writel(i2s, I2S_CLR, I2S_CLR_TXC); - } + if (i2s->trcm_tx_only) + { + i2s_update_bits(i2s, I2S_XFER, I2S_XFER_TXS_MASK | I2S_XFER_RXS_MASK, + I2S_XFER_TXS_STOP | I2S_XFER_RXS_STOP); + rockchip_i2s_tdm_clear_tx(i2s); + } + else + { + i2s_update_bits(i2s, I2S_XFER, I2S_XFER_TXS_MASK, I2S_XFER_TXS_STOP); + rockchip_i2s_tdm_clear_tx(i2s); + } - rt_hw_us_delay(150); + rt_hw_us_delay(150); + } i2s->running = RT_FALSE; - if (i2s->mclk_tx) + if (!i2s->always_on && i2s->mclk_tx && i2s->mclk_enabled) { rt_clk_disable_unprepare(i2s->mclk_tx); + i2s->mclk_enabled = RT_FALSE; } return RT_EOK; @@ -315,6 +329,7 @@ rt_ssize_t rockchip_i2s_tdm_push(struct rockchip_i2s_tdm *i2s, const rt_uint8_t *buf, rt_size_t size) { rt_size_t i = 0; + rt_uint32_t wait_us = 0; rt_uint32_t fifo; if (!i2s || !buf || !size) @@ -328,12 +343,28 @@ rt_ssize_t rockchip_i2s_tdm_push(struct rockchip_i2s_tdm *i2s, if (fifo >= I2S_FIFO_DEPTH) { - rt_thread_mdelay(1); + wait_us += I2S_PIO_WAIT_US; + if (wait_us > I2S_PIO_TIMEOUT_US) + { + LOG_E("TX FIFO stalled: fifo=%u xfer=%08x ckr=%08x txcr=%08x div=%08x", + fifo, i2s_readl(i2s, I2S_XFER), i2s_readl(i2s, I2S_CKR), + i2s_readl(i2s, I2S_TXCR), i2s_readl(i2s, I2S_CLKDIV)); + return -RT_ETIMEOUT; + } + rt_hw_us_delay(I2S_PIO_WAIT_US); continue; } + wait_us = 0; i2s_writel(i2s, I2S_TXDR, buf[i] | (buf[i + 1] << 8)); i += 2; + + /* + * The RK3588 HDMI I2S FIFO level can stay at zero in PIO mode. + * Pace each channel word from the configured sample rate so writes + * cannot overrun a FIFO whose level is not observable. + */ + rt_hw_us_delay(i2s->pio_word_delay_us); } return i; @@ -361,9 +392,28 @@ rt_err_t rockchip_i2s_tdm_dma_enable(struct rockchip_i2s_tdm *i2s) return -RT_EINVAL; } + /* + * The HDMI controller drops its first frame-sync cycle. Seed one + * complete stereo S16 frame before enabling its DMA request, matching + * the Rockchip Linux driver's HDMI-path workaround. + */ + if (i2s->hdmi_path) + { + i2s_writel(i2s, I2S_TXDR, 0); + } + + i2s->tx_underruns = 0; + i2s_update_bits(i2s, I2S_INTCR, I2S_INTCR_TXUIC, I2S_INTCR_TXUIC); + i2s_update_bits(i2s, I2S_DMACR, I2S_DMACR_TDL_MASK | I2S_DMACR_TDE_MASK, I2S_DMACR_TDL(16) | I2S_DMACR_TDE_ENABLE); + /* Give DMA a head start before the transmitter consumes the FIFO. */ + if (i2s->hdmi_path) + { + rt_hw_us_delay(1); + } + return RT_EOK; } @@ -379,6 +429,25 @@ rt_err_t rockchip_i2s_tdm_dma_disable(struct rockchip_i2s_tdm *i2s) return RT_EOK; } +void rockchip_i2s_tdm_dma_check_underrun(struct rockchip_i2s_tdm *i2s) +{ + if (!i2s) + { + return; + } + + if (i2s_readl(i2s, I2S_INTSR) & I2S_INTSR_TXUI) + { + i2s->tx_underruns++; + i2s_update_bits(i2s, I2S_INTCR, I2S_INTCR_TXUIC, I2S_INTCR_TXUIC); + } +} + +rt_uint32_t rockchip_i2s_tdm_dma_underruns(struct rockchip_i2s_tdm *i2s) +{ + return i2s ? i2s->tx_underruns : 0; +} + static rt_err_t rockchip_i2s_tdm_probe(struct rt_platform_device *pdev) { rt_err_t err; @@ -397,6 +466,8 @@ static rt_err_t rockchip_i2s_tdm_probe(struct rt_platform_device *pdev) i2s->parent.ofw_node = dev->ofw_node; i2s->trcm_tx_only = rt_dm_dev_prop_read_bool(dev, "rockchip,trcm-sync-tx-only"); i2s->playback_only = rt_dm_dev_prop_read_bool(dev, "rockchip,playback-only"); + i2s->always_on = rt_dm_dev_prop_read_bool(dev, "rockchip,always-on"); + i2s->hdmi_path = rt_dm_dev_prop_read_bool(dev, "rockchip,hdmi-path"); i2s->regs = rt_dm_dev_iomap(dev, 0); @@ -430,6 +501,28 @@ static rt_err_t rockchip_i2s_tdm_probe(struct rt_platform_device *pdev) goto _fail; } + if (i2s->always_on) + { + const struct rockchip_i2s_config default_cfg = + { + .samplerate = 48000, + .channels = 2, + .samplebits = 16, + .mclk_fs = I2S_DEFAULT_MCLK_FS, + }; + + if ((err = rockchip_i2s_tdm_hw_params(i2s, &default_cfg))) + { + goto _fail; + } + + /* This must follow every register configuration on HDMI I2S. */ + if ((err = rockchip_i2s_tdm_start(i2s))) + { + goto _fail; + } + } + i2s->dma_tx = rt_dma_chan_request(dev, "tx"); if (rt_is_err_or_null(i2s->dma_tx)) diff --git a/bsp/rockchip/dm/audio/rockchip-i2s-tdm.h b/bsp/rockchip/dm/audio/rockchip-i2s-tdm.h index 73b268a9e0dc..63fe5465c832 100644 --- a/bsp/rockchip/dm/audio/rockchip-i2s-tdm.h +++ b/bsp/rockchip/dm/audio/rockchip-i2s-tdm.h @@ -55,6 +55,9 @@ #define I2S_DMACR_TDL(x) ((x) << 0) #define I2S_DMACR_TDL_MASK (0x1f << 0) +#define I2S_INTCR_TXUIC RT_BIT(2) +#define I2S_INTSR_TXUI RT_BIT(1) + #define I2S_XFER_TXS_START (1 << 0) #define I2S_XFER_TXS_STOP (0 << 0) #define I2S_XFER_TXS_MASK (1 << 0) @@ -81,6 +84,7 @@ struct rockchip_i2s_config struct rockchip_i2s_tdm; +#ifdef RT_AUDIO_ROCKCHIP_I2S_TDM rt_err_t rockchip_i2s_tdm_hw_params(struct rockchip_i2s_tdm *i2s, const struct rockchip_i2s_config *cfg); rt_err_t rockchip_i2s_tdm_start(struct rockchip_i2s_tdm *i2s); @@ -88,12 +92,62 @@ rt_err_t rockchip_i2s_tdm_stop(struct rockchip_i2s_tdm *i2s); rt_ssize_t rockchip_i2s_tdm_push(struct rockchip_i2s_tdm *i2s, const rt_uint8_t *buf, rt_size_t size); struct rockchip_i2s_tdm *rockchip_i2s_tdm_find(struct rt_ofw_node *np); +#else +rt_inline rt_err_t rockchip_i2s_tdm_hw_params(struct rockchip_i2s_tdm *i2s, + const struct rockchip_i2s_config *cfg) +{ + return -RT_ENOSYS; +} +rt_inline rt_err_t rockchip_i2s_tdm_start(struct rockchip_i2s_tdm *i2s) +{ + return -RT_ENOSYS; +} +rt_inline rt_err_t rockchip_i2s_tdm_stop(struct rockchip_i2s_tdm *i2s) +{ + return -RT_ENOSYS; +} +rt_inline rt_ssize_t rockchip_i2s_tdm_push(struct rockchip_i2s_tdm *i2s, + const rt_uint8_t *buf, rt_size_t size) +{ + return -RT_ENOSYS; +} +rt_inline struct rockchip_i2s_tdm *rockchip_i2s_tdm_find(struct rt_ofw_node *np) +{ + return RT_NULL; +} +#endif -#ifdef RT_USING_DMA +#if defined(RT_USING_DMA) && defined(RT_AUDIO_ROCKCHIP_I2S_TDM) struct rt_dma_chan *rockchip_i2s_tdm_get_dma_tx(struct rockchip_i2s_tdm *i2s); rt_ubase_t rockchip_i2s_tdm_txdr_phys(struct rockchip_i2s_tdm *i2s); rt_err_t rockchip_i2s_tdm_dma_enable(struct rockchip_i2s_tdm *i2s); rt_err_t rockchip_i2s_tdm_dma_disable(struct rockchip_i2s_tdm *i2s); +void rockchip_i2s_tdm_dma_check_underrun(struct rockchip_i2s_tdm *i2s); +rt_uint32_t rockchip_i2s_tdm_dma_underruns(struct rockchip_i2s_tdm *i2s); +#else +rt_inline struct rt_dma_chan *rockchip_i2s_tdm_get_dma_tx(struct rockchip_i2s_tdm *i2s) +{ + return RT_NULL; +} +rt_inline rt_ubase_t rockchip_i2s_tdm_txdr_phys(struct rockchip_i2s_tdm *i2s) +{ + return 0; +} +rt_inline rt_err_t rockchip_i2s_tdm_dma_enable(struct rockchip_i2s_tdm *i2s) +{ + return -RT_ENOSYS; +} +rt_inline rt_err_t rockchip_i2s_tdm_dma_disable(struct rockchip_i2s_tdm *i2s) +{ + return -RT_ENOSYS; +} +rt_inline void rockchip_i2s_tdm_dma_check_underrun(struct rockchip_i2s_tdm *i2s) +{ +} +rt_inline rt_uint32_t rockchip_i2s_tdm_dma_underruns(struct rockchip_i2s_tdm *i2s) +{ + return 0; +} #endif #endif /* __ROCKCHIP_I2S_TDM_H__ */ diff --git a/bsp/rockchip/dm/audio/rockchip-sai.c b/bsp/rockchip/dm/audio/rockchip-sai.c new file mode 100644 index 000000000000..be3684444697 --- /dev/null +++ b/bsp/rockchip/dm/audio/rockchip-sai.c @@ -0,0 +1,396 @@ +/* + * Copyright (c) 2006-2026, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2026-3-15 GuEe-GUI the first version + */ + +#include + +#define DBG_TAG "audio.sai" +#define DBG_LVL DBG_INFO +#include + +#include "rockchip-sai.h" +#include "rockchip-audio-clock.h" + +#define SAI_TXCR 0x00 +#define SAI_FSCR 0x04 +#define SAI_MONO_CR 0x0c +#define SAI_XFER 0x10 +#define SAI_CLR 0x14 +#define SAI_CKR 0x18 +#define SAI_DMACR 0x24 +#define SAI_INTCR 0x28 +#define SAI_TXDR 0x30 +#define SAI_PATH_SEL 0x38 +#define SAI_TX_SLOT_MASK0 0x3c +#define SAI_TX_SHIFT 0x64 +#define SAI_VERSION 0x70 +#define SAI_STATUS 0x6c + +#define SAI_TXCR_I2S (RT_BIT(22) | RT_BIT(10)) +#define SAI_TXCR_STEREO RT_BIT(11) +#define SAI_TXCR_SLOT_32 (31U << 5) +#define SAI_FSCR_STEREO_32 (RT_BIT(24) | (31U << 12) | 63U) +#define SAI_CLK_EN RT_BIT(0) +#define SAI_FS_EN RT_BIT(1) +#define SAI_TX_EN RT_BIT(2) +#define SAI_TX_IDLE RT_BIT(7) +#define SAI_FS_IDLE RT_BIT(6) +#define SAI_TX_CLEAR RT_BIT(0) +#define SAI_DMA_TX_EN RT_BIT(8) +#define SAI_DMA_THRESHOLD 16U +#define SAI_TX_UNDERRUN_CLR RT_BIT(2) +#define SAI_VER_2307 0x23073576 +#define SAI_VER_2311 0x23112118 + +struct rockchip_sai +{ + rt_list_t list; + struct rt_ofw_node *np; + void *regs; + rt_uint64_t phys; + struct rt_clk *hclk; + struct rt_clk *mclk; + struct rt_reset_control *rst_h; + struct rt_reset_control *rst_m; + struct rt_dma_chan *dma_tx; + rt_uint32_t version; + rt_bool_t configured; + rt_bool_t running; +}; + +static rt_list_t sai_list = RT_LIST_OBJECT_INIT(sai_list); +static RT_DEFINE_SPINLOCK(sai_lock); + +static rt_err_t sai_poll(struct rockchip_sai *sai, rt_uint32_t reg, + rt_uint32_t mask, rt_uint32_t value) +{ + int timeout; + + for (timeout = 0; timeout < 1000; ++timeout) + { + if ((HWREG32(sai->regs + reg) & mask) == value) + { + return RT_EOK; + } + rt_hw_us_delay(1); + } + LOG_E("%s: register %x timeout", sai->np->name, reg); + return -RT_ETIMEOUT; +} + +static rt_err_t sai_idle(struct rockchip_sai *sai, rt_bool_t frame) +{ + rt_uint32_t reg = SAI_XFER; + rt_uint32_t mask = frame ? SAI_FS_IDLE : SAI_TX_IDLE; + + if (sai->version >= SAI_VER_2307) + { + reg = SAI_STATUS; + mask = frame ? RT_BIT(1) : RT_BIT(2); + if (sai->version >= SAI_VER_2311) + { + mask >>= 1; + } + } + return sai_poll(sai, reg, mask, mask); +} + +struct rockchip_sai *rockchip_sai_find(struct rt_ofw_node *np) +{ + struct rockchip_sai *sai; + struct rockchip_sai *found; + + found = RT_NULL; + + rt_spin_lock(&sai_lock); + rt_list_for_each_entry(sai, &sai_list, list) + { + if (sai->np == np) + { + found = sai; + break; + } + } + rt_spin_unlock(&sai_lock); + return found; +} + +struct rt_dma_chan *rockchip_sai_dma_tx(struct rockchip_sai *sai) +{ + return sai->dma_tx; +} + +rt_ubase_t rockchip_sai_txdr_phys(struct rockchip_sai *sai) +{ + return sai->phys + SAI_TXDR; +} + +rt_err_t rockchip_sai_hw_params(struct rockchip_sai *sai, + const struct rockchip_i2s_config *cfg) +{ + rt_err_t err; + rt_uint32_t mclk; + rt_uint32_t div; + rt_uint64_t actual; + rt_uint64_t delta; + + if (cfg->channels != 2 || cfg->samplebits != 16 || + (cfg->samplerate != 44100 && cfg->samplerate != 48000) || + (cfg->mclk_fs != 128 && cfg->mclk_fs != 256)) + { + return -RT_EINVAL; + } + if (sai->running) + { + return -RT_EBUSY; + } + + sai->configured = RT_FALSE; + mclk = cfg->samplerate * cfg->mclk_fs; + if ((err = rockchip_audio_set_mclk(sai->mclk, mclk))) + { + return err; + } + actual = rt_clk_get_rate(sai->mclk); + delta = actual > mclk ? actual - mclk : mclk - actual; + if (delta > mclk / 100000) + { + LOG_E("%s: mclk=%lu, expected %u", sai->np->name, + (rt_ubase_t)actual, mclk); + return -RT_EINVAL; + } + div = cfg->mclk_fs / 64; + + HWREG32(sai->regs + SAI_CKR) = (div - 1) << 3; + HWREG32(sai->regs + SAI_TXCR) = SAI_TXCR_I2S | SAI_TXCR_STEREO | + SAI_TXCR_SLOT_32 | 15; + HWREG32(sai->regs + SAI_FSCR) = SAI_FSCR_STEREO_32; + HWREG32(sai->regs + SAI_TX_SHIFT) = 2; + HWREG32(sai->regs + SAI_MONO_CR) = 0; + HWREG32(sai->regs + SAI_DMACR) = SAI_DMA_THRESHOLD; + sai->configured = RT_TRUE; + return RT_EOK; +} + +rt_err_t rockchip_sai_start(struct rockchip_sai *sai) +{ + if (!sai->configured) + { + return -RT_EINVAL; + } + if (sai->running) + { + return RT_EOK; + } + + /* Allow the divider to settle before enabling BCLK and frame sync. */ + rt_hw_us_delay(20); + HWREG32(sai->regs + SAI_XFER) = SAI_CLK_EN | SAI_FS_EN; + HWREG32(sai->regs + SAI_INTCR) = SAI_TX_UNDERRUN_CLR; + HWREG32(sai->regs + SAI_DMACR) = SAI_DMA_THRESHOLD | SAI_DMA_TX_EN; + HWREG32(sai->regs + SAI_XFER) = SAI_CLK_EN | SAI_FS_EN | SAI_TX_EN; + sai->running = RT_TRUE; + return RT_EOK; +} + +rt_err_t rockchip_sai_stop(struct rockchip_sai *sai) +{ + rt_err_t err; + rt_err_t result; + + if (!sai->running) + { + return RT_EOK; + } + + HWREG32(sai->regs + SAI_DMACR) = SAI_DMA_THRESHOLD; + HWREG32(sai->regs + SAI_XFER) = SAI_CLK_EN | SAI_FS_EN; + result = sai_idle(sai, RT_FALSE); + HWREG32(sai->regs + SAI_CLR) = SAI_TX_CLEAR; + err = sai_poll(sai, SAI_CLR, SAI_TX_CLEAR, 0); + if (!result) + { + result = err; + } + HWREG32(sai->regs + SAI_XFER) = 0; + err = sai_idle(sai, RT_TRUE); + if (!result) + { + result = err; + } + rt_hw_us_delay(40); + sai->running = RT_FALSE; + if (result) + { + sai->configured = RT_FALSE; + } + return result; +} + +static rt_err_t sai_reset(struct rt_reset_control *rst) +{ + rt_err_t err; + + if ((err = rt_reset_control_assert(rst))) + { + return err; + } + rt_hw_us_delay(10); + err = rt_reset_control_deassert(rst); + rt_hw_us_delay(10); + return err; +} + +static rt_err_t rockchip_sai_probe(struct rt_platform_device *pdev) +{ + rt_err_t err; + struct rt_device *dev = &pdev->parent; + struct rockchip_sai *sai; + int i; + + sai = rt_calloc(1, sizeof(*sai)); + + if (!sai) + { + return -RT_ENOMEM; + } + sai->np = dev->ofw_node; + sai->regs = rt_dm_dev_iomap(dev, 0); + if (!sai->regs) + { + err = -RT_EIO; + goto _free; + } + if ((err = rt_dm_dev_get_address(dev, 0, &sai->phys, RT_NULL))) + { + goto _unmap; + } + sai->hclk = rt_clk_get_by_name(dev, "hclk"); + sai->mclk = rt_clk_get_by_name(dev, "mclk"); + sai->rst_h = rt_reset_control_get_by_name(dev, "h"); + sai->rst_m = rt_reset_control_get_by_name(dev, "m"); + if (rt_is_err(sai->hclk) || rt_is_err(sai->mclk) || + rt_is_err(sai->rst_h) || rt_is_err(sai->rst_m)) + { + err = -RT_EIO; + LOG_E("%s: missing clock or reset", sai->np->name); + goto _put; + } + if ((err = rt_clk_prepare_enable(sai->hclk))) + { + goto _put; + } + if ((err = rt_clk_prepare_enable(sai->mclk))) + { + goto _disable_hclk; + } + /* Reset the bus domain first to restore master mode before resetting MCLK. */ + if ((err = sai_reset(sai->rst_h)) || (err = sai_reset(sai->rst_m))) + { + goto _disable_mclk; + } + sai->version = HWREG32(sai->regs + SAI_VERSION); + HWREG32(sai->regs + SAI_XFER) = 0; + HWREG32(sai->regs + SAI_DMACR) = 0; + HWREG32(sai->regs + SAI_INTCR) = SAI_TX_UNDERRUN_CLR; + HWREG32(sai->regs + SAI_PATH_SEL) = 0xe4e4; + for (i = 0; i < 4; ++i) + { + HWREG32(sai->regs + SAI_TX_SLOT_MASK0 + i * 4) = 0; + } + + sai->dma_tx = rt_dma_chan_request(dev, "tx"); + if (rt_is_err_or_null(sai->dma_tx)) + { + err = rt_is_err(sai->dma_tx) ? rt_ptr_err(sai->dma_tx) : -RT_ENOSYS; + LOG_E("%s: TX DMA unavailable (%d)", sai->np->name, err); + goto _disable_mclk; + } + rt_spin_lock(&sai_lock); + rt_list_insert_before(&sai_list, &sai->list); + rt_spin_unlock(&sai_lock); + dev->user_data = sai; + rt_dm_dev_bind_fwdata(dev, RT_NULL, sai); + LOG_D("%s: SAI playback ready, version=%08x", sai->np->name, sai->version); + return RT_EOK; + +_disable_mclk: + rt_clk_disable_unprepare(sai->mclk); +_disable_hclk: + rt_clk_disable_unprepare(sai->hclk); +_put: + if (!rt_is_err_or_null(sai->rst_m)) + { + rt_reset_control_put(sai->rst_m); + } + if (!rt_is_err_or_null(sai->rst_h)) + { + rt_reset_control_put(sai->rst_h); + } + if (!rt_is_err_or_null(sai->mclk)) + { + rt_clk_put(sai->mclk); + } + if (!rt_is_err_or_null(sai->hclk)) + { + rt_clk_put(sai->hclk); + } +_unmap: + rt_iounmap(sai->regs); +_free: + rt_free(sai); + return err; +} + +static rt_err_t rockchip_sai_remove(struct rt_platform_device *pdev) +{ + struct rockchip_sai *sai = pdev->parent.user_data; + rt_err_t err; + + err = rt_dma_chan_stop(sai->dma_tx); + if (!err) + { + err = rockchip_sai_stop(sai); + } + if (err) + { + return err; + } + rt_dma_chan_release(sai->dma_tx); + rt_dm_dev_unbind_fwdata(&pdev->parent, RT_NULL); + rt_spin_lock(&sai_lock); + rt_list_remove(&sai->list); + rt_spin_unlock(&sai_lock); + rt_reset_control_assert(sai->rst_m); + rt_reset_control_assert(sai->rst_h); + rt_reset_control_put(sai->rst_m); + rt_reset_control_put(sai->rst_h); + rt_clk_disable_unprepare(sai->mclk); + rt_clk_disable_unprepare(sai->hclk); + rt_clk_put(sai->mclk); + rt_clk_put(sai->hclk); + rt_iounmap(sai->regs); + rt_free(sai); + return RT_EOK; +} + +static const struct rt_ofw_node_id rockchip_sai_ids[] = +{ + { .compatible = "rockchip,rk3528-sai" }, + { /* sentinel */ } +}; + +static struct rt_platform_driver rockchip_sai_driver = +{ + .name = "rockchip-sai", + .ids = rockchip_sai_ids, + .probe = rockchip_sai_probe, + .remove = rockchip_sai_remove, +}; +RT_PLATFORM_DRIVER_EXPORT(rockchip_sai_driver); diff --git a/bsp/rockchip/dm/audio/rockchip-sai.h b/bsp/rockchip/dm/audio/rockchip-sai.h new file mode 100644 index 000000000000..e6a7f42dd4d2 --- /dev/null +++ b/bsp/rockchip/dm/audio/rockchip-sai.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2006-2026, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2026-3-15 GuEe-GUI the first version + */ + +#ifndef __ROCKCHIP_SAI_H__ +#define __ROCKCHIP_SAI_H__ + +#include "rockchip-i2s-tdm.h" + +struct rockchip_sai; + +#ifdef RT_AUDIO_ROCKCHIP_SAI +struct rockchip_sai *rockchip_sai_find(struct rt_ofw_node *np); +rt_err_t rockchip_sai_hw_params(struct rockchip_sai *sai, + const struct rockchip_i2s_config *cfg); +rt_err_t rockchip_sai_start(struct rockchip_sai *sai); +rt_err_t rockchip_sai_stop(struct rockchip_sai *sai); +struct rt_dma_chan *rockchip_sai_dma_tx(struct rockchip_sai *sai); +rt_ubase_t rockchip_sai_txdr_phys(struct rockchip_sai *sai); +#else +rt_inline struct rockchip_sai *rockchip_sai_find(struct rt_ofw_node *np) +{ + return RT_NULL; +} +rt_inline rt_err_t rockchip_sai_hw_params(struct rockchip_sai *sai, + const struct rockchip_i2s_config *cfg) +{ + return -RT_ENOSYS; +} +rt_inline rt_err_t rockchip_sai_start(struct rockchip_sai *sai) +{ + return -RT_ENOSYS; +} +rt_inline rt_err_t rockchip_sai_stop(struct rockchip_sai *sai) +{ + return -RT_ENOSYS; +} +rt_inline struct rt_dma_chan *rockchip_sai_dma_tx(struct rockchip_sai *sai) +{ + return RT_NULL; +} +rt_inline rt_ubase_t rockchip_sai_txdr_phys(struct rockchip_sai *sai) +{ + return 0; +} +#endif + +#endif /* __ROCKCHIP_SAI_H__ */ diff --git a/bsp/rockchip/dm/clk/clk-rk3588.c b/bsp/rockchip/dm/clk/clk-rk3588.c index 14a6005f40db..e9d2259002f8 100755 --- a/bsp/rockchip/dm/clk/clk-rk3588.c +++ b/bsp/rockchip/dm/clk/clk-rk3588.c @@ -1759,7 +1759,7 @@ static struct rt_clk_cell *rk3588_clk_cells[] = &rk3588_i2s9_8ch_rx_fracmux), [CLK_I2S9_8CH_RX] = &rk3588_i2s9_8ch_rx_fracmux.cell, [MCLK_I2S9_8CH_RX] = GATE(MCLK_I2S9_8CH_RX, "mclk_i2s9_8ch_rx", "clk_i2s9_8ch_rx", 0, RK3588_CLKGATE_CON(65), 3, GFLAGS), - [CLK_I2S5_8CH_TX_SRC] = COMPOSITE(CLK_I2S5_8CH_TX_SRC, "clk_i2s5_8ch_tx_src", gpll_aupll_p, 0, + [CLK_I2S5_8CH_TX_SRC] = COMPOSITE(CLK_I2S5_8CH_TX_SRC, "clk_i2s5_8ch_tx_src", gpll_aupll_p, RT_CLK_F_SET_RATE_NO_REPARENT, RK3588_CLKSEL_CON(140), 10, 1, MFLAGS, 5, 5, DFLAGS, RK3588_CLKGATE_CON(62), 6, GFLAGS), [CLK_I2S5_8CH_TX_FRAC] = COMPOSITE_FRACMUX(CLK_I2S5_8CH_TX_FRAC, "clk_i2s5_8ch_tx_frac", "clk_i2s5_8ch_tx_src", RT_CLK_F_SET_RATE_PARENT, diff --git a/bsp/rockchip/dm/graphic/framebuffer/SConscript b/bsp/rockchip/dm/graphic/framebuffer/SConscript index 4d40b80218a2..72bba560a830 100755 --- a/bsp/rockchip/dm/graphic/framebuffer/SConscript +++ b/bsp/rockchip/dm/graphic/framebuffer/SConscript @@ -24,7 +24,7 @@ if GetDepend(['RT_GRAPHIC_CONNECTOR_DSI2']): src += ['dsi2-rockchip.c'] if GetDepend(['RT_GRAPHIC_CONNECTOR_HDMI']): - src += ['hdmi-rockchip.c'] + src += ['hdmi-rockchip.c', 'hdmi-rk3588.c'] if GetDepend(['RT_GRAPHIC_PANEL_SIMPLE']): src += ['panel-simple.c'] diff --git a/bsp/rockchip/dm/graphic/framebuffer/display-rockchip.c b/bsp/rockchip/dm/graphic/framebuffer/display-rockchip.c index 270889a839db..517f8f08df86 100644 --- a/bsp/rockchip/dm/graphic/framebuffer/display-rockchip.c +++ b/bsp/rockchip/dm/graphic/framebuffer/display-rockchip.c @@ -33,7 +33,7 @@ static const char *display_node_name(struct rt_ofw_node *np) return np ? rt_ofw_node_full_name(np) : ""; } -static rt_bool_t display_is_legacy_hdmi(struct rt_ofw_node *np) +static rt_bool_t display_is_hdmi(struct rt_ofw_node *np) { if (!np) { @@ -41,28 +41,49 @@ static rt_bool_t display_is_legacy_hdmi(struct rt_ofw_node *np) } return rt_ofw_node_is_compatible(np, "rockchip,rk3568-dw-hdmi") || - rt_ofw_node_is_compatible(np, "rockchip,rk3528-dw-hdmi"); + rt_ofw_node_is_compatible(np, "rockchip,rk3528-dw-hdmi") || + rt_ofw_node_is_compatible(np, "rockchip,rk3588-dw-hdmi"); } static rt_bool_t display_try_fallback_timing(const struct display_route *route, struct display_timing *tim) { - if (!route || !tim || !display_is_legacy_hdmi(route->connector_np)) + if (!route || !tim || !display_is_hdmi(route->connector_np)) { return RT_FALSE; } rt_memset(tim, 0, sizeof(*tim)); - tim->pixelclock = 74250000; - tim->hactive = 1280; - tim->hfront_porch = 110; - tim->hsync_len = 40; - tim->hback_porch = 220; - tim->vactive = 720; - tim->vfront_porch = 5; + + if (rt_ofw_node_is_compatible(route->connector_np, "rockchip,rk3528-dw-hdmi")) + { + /* Conservative RGB8 bring-up mode, independent of the ROCK 5B sink. */ + tim->pixelclock = 74250000; + tim->hactive = 1280; + tim->hfront_porch = 110; + tim->hsync_len = 40; + tim->hback_porch = 220; + tim->vactive = 720; + tim->vfront_porch = 5; + tim->vsync_len = 5; + tim->vback_porch = 20; + tim->flags = DISPLAY_TIMING_F_PHSYNC | DISPLAY_TIMING_F_PVSYNC; + display_timing_derive(tim); + return RT_TRUE; + } + + /* Native timing advertised by the Rock 5B HDMI0 sink. */ + tim->pixelclock = 241500000; + tim->hactive = 2560; + tim->hfront_porch = 48; + tim->hsync_len = 32; + tim->hback_porch = 80; + tim->vactive = 1440; + tim->vfront_porch = 3; tim->vsync_len = 5; - tim->vback_porch = 20; - tim->flags = DISPLAY_TIMING_F_PHSYNC | DISPLAY_TIMING_F_PVSYNC; + tim->vback_porch = 33; + /* The ROCK 5B HDMI0 sink's native 2560x1440 mode is +HSync/-VSync. */ + tim->flags = DISPLAY_TIMING_F_PHSYNC; display_timing_derive(tim); return RT_TRUE; @@ -221,8 +242,8 @@ rt_err_t rockchip_display_route_enable(struct rt_ofw_node *route_np) { if (display_try_fallback_timing(&route, &tim)) { - LOG_D("route %s: fallback timing 1280x720p60 for %s", - display_node_name(route_np), + LOG_D("route %s: fallback timing %ux%up60 for %s", + display_node_name(route_np), tim.hactive, tim.vactive, display_node_name(route.connector_np)); } else @@ -492,7 +513,7 @@ static rt_bool_t display_route_is_legacy_hdmi(struct rt_ofw_node *route_np) conn = display_route_find_connector(route_np); - return conn && display_is_legacy_hdmi(conn->np); + return conn && display_is_hdmi(conn->np); } static int display_route_dsi_id(struct rt_ofw_node *route_np) @@ -591,7 +612,12 @@ static rt_bool_t display_route_matches(struct rt_ofw_node *route_np, struct rockchip_display_entity *conn; conn = display_route_find_connector(route_np); - if (!conn || !conn->conn_ops || !conn->conn_ops->detect) + if (!conn || !conn->conn_ops) + { + return RT_FALSE; + } + + if (!conn->conn_ops->detect) { return pass == DISPLAY_ROUTE_FIXED; } @@ -704,9 +730,48 @@ static rt_uint32_t display_enable_routes(void) } } + rt_ofw_node_put(route_np); return enabled_count; } +static rt_uint32_t display_enable_hdmi0_fallback(void) +{ + struct rt_ofw_node *routes_np; + struct rt_ofw_node *route_np; + rt_err_t err; + + if (!display_pdev) + { + return 0; + } + + routes_np = rt_ofw_get_child_by_tag(display_pdev->parent.ofw_node, "route"); + route_np = routes_np ? rt_ofw_get_child_by_tag(routes_np, "route-hdmi0") : RT_NULL; + rt_ofw_node_put(routes_np); + + if (!route_np) + { + return 0; + } + + /* + * HDMI0 is the explicit auto-mode fallback on RK3588 boards. Board + * firmware may leave the route node disabled while still exposing the + * complete endpoint graph, so do not discard this selected route here. + */ + LOG_D("try route-hdmi0"); + err = rockchip_display_route_enable(route_np); + rt_ofw_node_put(route_np); + + if (err) + { + LOG_W("route-hdmi0 fallback failed: %s", rt_strerror(err)); + return 0; + } + + return 1; +} + static rt_err_t display_subsys_probe(struct rt_platform_device *pdev) { display_pdev = pdev; @@ -716,11 +781,47 @@ static rt_err_t display_subsys_probe(struct rt_platform_device *pdev) static int rockchip_display_routes_init(void) { + enum display_route_policy policy = display_route_get_policy(); + +#ifdef RT_PHYE_ROCKCHIP_INNO_HDMI + /* VOP and HDMI can precede the external PHY/clk-port provider in OFW. */ + static const char * const rk3528_consumers[] = + { + "rockchip,rk3528-vop", "rockchip,rk3528-dw-hdmi", + }; + rt_size_t i; + + for (i = 0; i < sizeof(rk3528_consumers) / sizeof(rk3528_consumers[0]); ++i) + { + struct rt_ofw_node *np = rt_ofw_find_node_by_compatible(RT_NULL, + rk3528_consumers[i]); + + if (np && rt_ofw_node_is_available(np)) + { + rt_err_t err = rt_ofw_clk_set_defaults(np); + if (err) + LOG_W("%s clock defaults: %s", display_node_name(np), rt_strerror(err)); + if (!np->dev || !np->dev->drv) + rt_platform_ofw_request(np); + } + rt_ofw_node_put(np); + } +#endif /* RT_PHYE_ROCKCHIP_INNO_HDMI */ + /* * DSI retries deferred probing at component level 4. Enable every display * policy afterwards, but before environment level 5 can wait for rootfs. */ - display_enable_routes(); + if ((policy == DISPLAY_POLICY_AUTO || policy == DISPLAY_POLICY_HDMI) && + display_enable_hdmi0_fallback()) + { + return 0; + } + + if (!display_enable_routes()) + { + display_enable_hdmi0_fallback(); + } return 0; } diff --git a/bsp/rockchip/dm/graphic/framebuffer/fb-vop2-vp.c b/bsp/rockchip/dm/graphic/framebuffer/fb-vop2-vp.c index 139d94a4cf32..2778549c6480 100644 --- a/bsp/rockchip/dm/graphic/framebuffer/fb-vop2-vp.c +++ b/bsp/rockchip/dm/graphic/framebuffer/fb-vop2-vp.c @@ -10,6 +10,8 @@ #include "fb-vop2-vp.h" +#include + #define DBG_TAG "fb.vop2.vp" #define DBG_LVL DBG_INFO #include @@ -18,9 +20,18 @@ #define VOP2_MIX0_SRC_ALPHA 0x658 #define VOP2_MIX_ALPHA_STRIDE 0x8 -#define VOP2_VP_CLK_CTRL 0xC0C #define VOP2_PLL_LIMIT_HZ 594000000UL #define RK3568_NR_MIXERS 5 +#define RK3588_NR_MIXERS 7 + +#define RK3528_OVL_SYS_PORT_SEL 0x504 +#define RK3528_OVL_ESMART0_DLY 0x520 +#define RK3528_OVL_PORT0_CTRL 0x600 +#define RK3528_OVL_PORT0_LAYER_SEL 0x604 +#define RK3528_OVL_PORT0_BG_MIX 0x670 +#define RK3528_VP0_ACM_CTRL 0xcd0 +#define RK3528_SDR2HDR_CTRL 0x2010 +#define RK3528_HDRVIVID_CTRL 0x2040 static rt_uint16_t vop2fb_scl_cal_scale2(rt_uint32_t src, rt_uint32_t dst) { @@ -77,11 +88,15 @@ static rt_uint32_t vop2fb_vp_out_mode(struct vop2fb *vfb) } /* - * Keep RK3568 HDMI aligned with Linux/U-Boot: RGB888 over HDMI uses - * OUT_MODE_AAAA instead of plain P888. + * The Rockchip HDMI controllers consume the VP's 10-bit-capable + * parallel packing even when the source framebuffer is RGB8888 and + * TMDS is sent at 8 bpc. This is the mode selected by both the + * RK3588 QP HDMI and the earlier RK3568 HDMI connector drivers. */ - if (vfb->soc->soc == VOP2FB_SOC_RK3568 && - vfb->output_if == VOP2FB_OUTPUT_HDMI) + if ((vfb->soc->soc == VOP2FB_SOC_RK3528 || + vfb->soc->soc == VOP2FB_SOC_RK3568 || + vfb->soc->soc == VOP2FB_SOC_RK3588) && + vfb->output_if == VOP2FB_OUTPUT_HDMI) { return RK3568_VP_OUT_MODE_AAAA; } @@ -104,7 +119,9 @@ void vop2fb_output_disable(struct vop2fb *vfb, enum vop2fb_output_if output_if) { rt_uint32_t shift; - if (!vfb || !vfb->soc || vfb->soc->soc != VOP2FB_SOC_RK3568) + if (!vfb || !vfb->soc || + (vfb->soc->soc != VOP2FB_SOC_RK3568 && + vfb->soc->soc != VOP2FB_SOC_RK3528)) { return; } @@ -141,7 +158,8 @@ static void vop2fb_rk3568_if_cfg(struct vop2fb *vfb, rt_uint32_t vp_id, { rt_uint32_t dclk_inv, pol; - if (!vfb || !tim || vfb->soc->soc != VOP2FB_SOC_RK3568) + if (!vfb || !tim || (vfb->soc->soc != VOP2FB_SOC_RK3568 && + vfb->soc->soc != VOP2FB_SOC_RK3528)) { return; } @@ -154,7 +172,7 @@ static void vop2fb_rk3568_if_cfg(struct vop2fb *vfb, rt_uint32_t vp_id, * Later mode sets must preserve already-running outputs (for example * HDMI0 on VP0 while MIPI1 is attached to VP1). */ - if (!vfb->configured_vp_mask) + if (!vfb->configured_vp_mask && vfb->soc->soc == VOP2FB_SOC_RK3568) { vop2fb_rk3568_disable_outputs(vfb); } @@ -224,14 +242,93 @@ static void vop2fb_rk3568_if_cfg(struct vop2fb *vfb, rt_uint32_t vp_id, } } +static void vop2fb_rk3588_hdmi0_cfg(struct vop2fb *vfb, rt_uint32_t vp_id, + const struct display_timing *tim) +{ + rt_uint32_t pol; + + if (!vfb || !tim || !vfb->soc || vfb->soc->soc != VOP2FB_SOC_RK3588 || + vfb->output_if != VOP2FB_OUTPUT_HDMI) + { + return; + } + + /* + * HDMI0 is the fixed HDMI connector on Rock 5B. The interface clocks + * are sourced directly from the selected VP for the RGB888 TMDS path. + */ + pol = 0; + if (!(tim->flags & DISPLAY_TIMING_F_PHSYNC)) + { + pol |= RT_BIT(0); + } + if (!(tim->flags & DISPLAY_TIMING_F_PVSYNC)) + { + pol |= RT_BIT(1); + } + + vop2fb_mask_write(vfb, RK3568_DSP_IF_EN, 1, + RK3588_HDMI0_EN_SHIFT, 1); + vop2fb_mask_write(vfb, RK3568_DSP_IF_EN, RK3568_IF_MUX_MASK, + RK3588_HDMI0_MUX_SHIFT, vp_id); + /* + * RK3588 does not use the RK3568 common interface setup above. Program + * its HDMI0 fields here, matching the Linux VOP2 HDMI path. + */ + vop2fb_mask_write(vfb, RK3568_DSP_IF_POL, 1, + RK3568_IF_CTRL_REG_DONE_IMD_SHIFT, 1); + /* + * RK3588 routes HDMI sync polarity through VO1_GRF. The matching VOP + * fields are not implemented on this SoC; clear inherited RK3568 state + * rather than driving the obsolete HDMI clock/pin polarity bits. + */ + vop2fb_mask_write(vfb, RK3568_DSP_IF_POL, RK3568_IF_PIN_POL_MASK, + RK3568_IF_CRTL_HDMI_PIN_POL_SHIFT, 0); + vop2fb_mask_write(vfb, RK3568_DSP_IF_POL, 1, + RK3568_IF_CRTL_HDMI_DCLK_POL_SHIFT, 0); + + /* + * EDK2 Vop2CalcCruConfig() derives these encodings for HDMI RGB: + * the interface dclk is dclk / 4, its pixel clock is dclk / 2, and + * the VP core clock is dclk / 4. For 2560x1440@241.5 MHz this is + * 2, 1 and 2 respectively. + */ + vop2fb_mask_write(vfb, RK3568_DSP_IF_CTRL, 0x3, + RK3588_HDMI0_DCLK_DIV_SHIFT, 2); + vop2fb_mask_write(vfb, RK3568_DSP_IF_CTRL, 0x3, + RK3588_HDMI0_PIXCLK_DIV_SHIFT, 1); + vop2fb_reg_field(vfb, VOP2_VP_CLK_CTRL + vp_id * VOP2FB_VP_STRIDE, + 0x3, 0, 2); + vop2fb_reg_field(vfb, VOP2_VP_CLK_CTRL + vp_id * VOP2FB_VP_STRIDE, + 0x1, 2, 0); + + if (vfb->vop_grf) + { + rt_syscon_write(vfb->vop_grf, RK3588_GRF_VOP_CON2, + RK3588_GRF_VOP_HDMI0_EN | (RK3588_GRF_VOP_HDMI0_EN << 16)); + } + + if (vfb->vo1_grf) + { + rt_uint32_t mask = RK3588_GRF_VO1_HDMI_PIN_POL_MASK << + RK3588_GRF_VO1_HDMI0_PIN_POL_SHIFT; + + rt_syscon_write(vfb->vo1_grf, RK3588_GRF_VO1_CON0, + ((pol & RK3588_GRF_VO1_HDMI_PIN_POL_MASK) << + RK3588_GRF_VO1_HDMI0_PIN_POL_SHIFT) | (mask << 16)); + } +} + void vop2fb_vp_irq_ctrl(struct vop2fb *vfb, rt_uint32_t vp_id, rt_bool_t enable) { rt_uint32_t axi_irq = RK3568_SYS_BUS_ERROR_INTR; - rt_uint32_t clear_irqs = VOP2_VP_FS_FIELD_INTR | - VOP2_VP_POST_BUF_EMPTY_INTR; + rt_uint32_t clear_irqs = 0xffff; rt_uint32_t enable_irqs = VOP2_VP_FS_FIELD_INTR; - if (!vfb || vfb->soc->soc != VOP2FB_SOC_RK3568 || vp_id >= vfb->soc->vp_count) + if (!vfb || (vfb->soc->soc != VOP2FB_SOC_RK3528 && + vfb->soc->soc != VOP2FB_SOC_RK3568 && + vfb->soc->soc != VOP2FB_SOC_RK3588) || + vp_id >= vfb->soc->vp_count) { return; } @@ -239,7 +336,8 @@ void vop2fb_vp_irq_ctrl(struct vop2fb *vfb, rt_uint32_t vp_id, rt_bool_t enable) HWREG32((rt_uint8_t *)vfb->regs + RK3568_VP_INT_CLR(vp_id)) = (clear_irqs << 16) | clear_irqs; HWREG32((rt_uint8_t *)vfb->regs + RK3568_SYS0_INT_CLR) = axi_irq << 16 | axi_irq; - HWREG32((rt_uint8_t *)vfb->regs + RK3568_SYS1_INT_CLR) = axi_irq << 16 | axi_irq; + if (vfb->soc->soc != VOP2FB_SOC_RK3528) + HWREG32((rt_uint8_t *)vfb->regs + RK3568_SYS1_INT_CLR) = axi_irq << 16 | axi_irq; if (enable) { @@ -251,14 +349,16 @@ void vop2fb_vp_irq_ctrl(struct vop2fb *vfb, rt_uint32_t vp_id, rt_bool_t enable) HWREG32((rt_uint8_t *)vfb->regs + RK3568_VP_INT_EN(vp_id)) = (clear_irqs << 16) | enable_irqs; HWREG32((rt_uint8_t *)vfb->regs + RK3568_SYS0_INT_EN) = axi_irq << 16 | axi_irq; - HWREG32((rt_uint8_t *)vfb->regs + RK3568_SYS1_INT_EN) = axi_irq << 16 | axi_irq; + if (vfb->soc->soc != VOP2FB_SOC_RK3528) + HWREG32((rt_uint8_t *)vfb->regs + RK3568_SYS1_INT_EN) = axi_irq << 16 | axi_irq; } else { HWREG32((rt_uint8_t *)vfb->regs + RK3568_VP_INT_EN(vp_id)) = clear_irqs << 16; HWREG32((rt_uint8_t *)vfb->regs + RK3568_SYS0_INT_EN) = axi_irq << 16; - HWREG32((rt_uint8_t *)vfb->regs + RK3568_SYS1_INT_EN) = axi_irq << 16; + if (vfb->soc->soc != VOP2FB_SOC_RK3528) + HWREG32((rt_uint8_t *)vfb->regs + RK3568_SYS1_INT_EN) = axi_irq << 16; } } @@ -267,19 +367,24 @@ static void vop2fb_rk3568_dither_setup(struct vop2fb *vfb, rt_uint32_t vp_id) rt_uint32_t base; rt_bool_t pre_dither_down_en; - if (!vfb || !vfb->regs || !vfb->soc || vfb->soc->soc != VOP2FB_SOC_RK3568) + if (!vfb || !vfb->regs || !vfb->soc || + (vfb->soc->soc != VOP2FB_SOC_RK3528 && + vfb->soc->soc != VOP2FB_SOC_RK3568 && + vfb->soc->soc != VOP2FB_SOC_RK3588)) { return; } + base = VOP2_VP_DSP_CTRL + vp_id * VOP2FB_VP_STRIDE; + /* - * The current RT display stack only drives 24-bit RGB style outputs on - * RK3568. Match Linux/U-Boot and keep pre-dither enabled so VP0_DSP_CTRL - * aligns with the working Linux value (for HDMI 1280x720p60 this is - * 0x0001000f instead of 0x0000000f). + * HDMI uses the VP AAAA bus format on RK3568/RK3588. It already carries + * the full component width, so pre-dithering would change the bit layout + * observed by the HDMI parallel input. This matches the Rockchip + * U-Boot/EDK2 VOP2 setup for an HDMI RGB scanout. */ - pre_dither_down_en = vfb->output_if != VOP2FB_OUTPUT_NONE; - base = VOP2_VP_DSP_CTRL + vp_id * VOP2FB_VP_STRIDE; + pre_dither_down_en = vfb->output_if != VOP2FB_OUTPUT_NONE && + vop2fb_vp_out_mode(vfb) != RK3568_VP_OUT_MODE_AAAA; vop2fb_mask_write(vfb, base, 1, VOP2_VP_PRE_DITHER_DOWN_EN_SHIFT, pre_dither_down_en ? 1 : 0); @@ -324,8 +429,22 @@ static rt_uint32_t vop2fb_rk3568_pre_scan_max_dly(rt_uint32_t vp_id) } } +static rt_uint32_t vop2fb_pre_scan_max_dly(const struct vop2fb *vfb, + rt_uint32_t vp_id) +{ + if (vfb->soc->soc == VOP2FB_SOC_RK3588) + { + static const rt_uint32_t delay[] = { 42, 40, 52, 52 }; + + return delay[vp_id < RT_ARRAY_SIZE(delay) ? vp_id : 0]; + } + + return vop2fb_rk3568_pre_scan_max_dly(vp_id); +} + static rt_uint32_t vop2fb_rk3568_bg_ovl_dly(struct vop2fb *vfb, rt_uint32_t vp_id) { + rt_uint32_t port_sel; rt_uint32_t port_mux; if (!vfb || !vfb->regs) @@ -333,14 +452,25 @@ static rt_uint32_t vop2fb_rk3568_bg_ovl_dly(struct vop2fb *vfb, rt_uint32_t vp_i return 0; } - port_mux = (HWREG32((rt_uint8_t *)vfb->regs + VOP2_OVL_PORT_SEL) >> (vp_id * 4)) & 0xf; + /* OVL routing is shadowed. post_config runs before the next vblank. */ + if (vfb->regsbak && (VOP2_OVL_PORT_SEL >> 2) < vfb->soc->regbak_words) + { + port_sel = vfb->regsbak[VOP2_OVL_PORT_SEL >> 2]; + } + else + { + port_sel = HWREG32((rt_uint8_t *)vfb->regs + VOP2_OVL_PORT_SEL); + } + + port_mux = (port_sel >> (vp_id * 4)) & 0xf; if (port_mux >= RK3568_NR_MIXERS) { return 0; } - return (RK3568_NR_MIXERS - port_mux) << 1; + return ((vfb->soc->soc == VOP2FB_SOC_RK3588 ? 7 : RK3568_NR_MIXERS) - + port_mux) << 1; } rt_err_t vop2fb_vp_post_config(struct vop2fb *vfb, rt_uint32_t vp_id, @@ -361,16 +491,46 @@ rt_err_t vop2fb_vp_post_config(struct vop2fb *vfb, rt_uint32_t vp_id, rt_uint32_t pre_scan_dly; if (!vfb || !tim || !vfb->regs || !vfb->soc || - vfb->soc->soc != VOP2FB_SOC_RK3568) + (vfb->soc->soc != VOP2FB_SOC_RK3528 && + vfb->soc->soc != VOP2FB_SOC_RK3568 && + vfb->soc->soc != VOP2FB_SOC_RK3588)) { return RT_EOK; } hdisplay_round2 = (tim->hactive + 1) & ~1u; hsync_len = tim->hsync_len ? tim->hsync_len : 1; - bg_dly = vop2fb_rk3568_pre_scan_max_dly(vp_id); + if (vfb->soc->soc == VOP2FB_SOC_RK3588 && hsync_len < 8) + { + hsync_len = 8; + } + + hsync_start = tim->hactive + tim->hfront_porch; + vsync_start = tim->vactive + tim->vfront_porch; + hact_st = tim->htotal - hsync_start; + hact_end = hact_st + tim->hactive; + vact_st = tim->vtotal - vsync_start; + vact_end = vact_st + tim->vactive; + hscale = vop2fb_scl_cal_scale2(tim->hactive, tim->hactive); + vscale = vop2fb_scl_cal_scale2(tim->vactive, tim->vactive); + + bg_dly = vop2fb_pre_scan_max_dly(vfb, vp_id); bg_ovl_dly = vop2fb_rk3568_bg_ovl_dly(vfb, vp_id); + if (vfb->soc->soc == VOP2FB_SOC_RK3528) + { + /* SDR bypass: window(8) + layer mixer(6) + HDR mixer(2). */ + bg_dly = 16; + bg_ovl_dly = 0; + hsync_len = rt_max(hsync_len, 8U); + } + + if (vfb->soc->soc == VOP2FB_SOC_RK3588) + { + /* Single Esmart RGB route, verified by full-frame writeback. */ + bg_ovl_dly = 0; + } + if (bg_dly > bg_ovl_dly) { bg_dly -= bg_ovl_dly; @@ -381,16 +541,24 @@ rt_err_t vop2fb_vp_post_config(struct vop2fb *vfb, rt_uint32_t vp_id, } pre_scan_dly = bg_dly + (hdisplay_round2 >> 1) - 1; - hsync_start = tim->hactive + tim->hfront_porch; - vsync_start = tim->vactive + tim->vfront_porch; - hact_st = tim->htotal - hsync_start; - hact_end = hact_st + tim->hactive; - vact_st = tim->vtotal - vsync_start; - vact_end = vact_st + tim->vactive; - hscale = vop2fb_scl_cal_scale2(tim->hactive, tim->hactive); - vscale = vop2fb_scl_cal_scale2(tim->vactive, tim->vactive); - vop2fb_mask_write(vfb, RK3568_VP0_BG_MIX_CTRL + vp_id * 4, + /* RK3588 has no RK3568 post scaler; c34..c3c belong to DOVI timing. */ + if (vfb->soc->soc == VOP2FB_SOC_RK3588) + { + vop2fb_mask_write(vfb, RK3568_VP0_BG_MIX_CTRL + vp_id * 4, + RK3568_BG_MIX_CTRL_MASK, RK3568_BG_MIX_CTRL_SHIFT, bg_dly); + vop2fb_reg_write(vfb, VOP2_VP_DSP_BG + vp_id * VOP2FB_VP_STRIDE, + 0); + vop2fb_reg_write(vfb, RK3568_VP0_PRE_SCAN_HTIMING + + vp_id * VOP2FB_VP_STRIDE, + (pre_scan_dly << 16) | hsync_len); + vop2fb_reg_write(vfb, VOP2_REG_CFG_DONE, + vop2fb_cfg_done_vp_val(vfb, vp_id)); + return RT_EOK; + } + + vop2fb_mask_write(vfb, vfb->soc->soc == VOP2FB_SOC_RK3528 ? + RK3528_OVL_PORT0_BG_MIX : RK3568_VP0_BG_MIX_CTRL + vp_id * 4, RK3568_BG_MIX_CTRL_MASK, RK3568_BG_MIX_CTRL_SHIFT, bg_dly); vop2fb_reg_write(vfb, VOP2_VP_DSP_BG + vp_id * VOP2FB_VP_STRIDE, 0); vop2fb_reg_write(vfb, RK3568_VP0_POST_DSP_HACT_INFO + vp_id * VOP2FB_VP_STRIDE, @@ -453,6 +621,53 @@ static rt_err_t vop2fb_vp_dclk_set_rate(struct vop2fb *vfb, rt_uint32_t vp_id, r if (rt_is_err_or_null(dclk)) { LOG_W("VP%u dclk not found", vp_id); + return vfb->soc->soc == VOP2FB_SOC_RK3528 ? -RT_ENOENT : RT_EOK; + } + + if ((vfb->soc->soc == VOP2FB_SOC_RK3588 || + vfb->soc->soc == VOP2FB_SOC_RK3528) && + vfb->output_if == VOP2FB_OUTPUT_HDMI) + { + struct rt_clk *pixel_clk; + + if (vfb->soc->soc == VOP2FB_SOC_RK3528) + rt_strncpy(name, "clk_hdmiphy_pixel_io", sizeof(name)); + else + rt_snprintf(name, sizeof(name), "clk_hdmiphy_pixel%u", 0U); + pixel_clk = rt_clk_get_by_name(dev, name); + if (rt_is_err_or_null(pixel_clk)) + { + LOG_E("VP%u HDMI pixel clock unavailable", vp_id); + rt_clk_put(dclk); + return -RT_EIO; + } + + if ((err = rt_clk_set_parent(dclk, pixel_clk)) || + (err = rt_clk_set_rate(pixel_clk, rate))) + { + LOG_E("VP%u HDMI clock %lu failed: %s", vp_id, + rate, rt_strerror(err)); + rt_clk_put(pixel_clk); + rt_clk_put(dclk); + return err; + } + + actual_rate = rt_clk_get_rate(dclk); + pll_rate = rt_clk_get_rate(pixel_clk); + if (!actual_rate || !pll_rate || + (actual_rate > rate ? actual_rate - rate : rate - actual_rate) > rate / 1000 || + (pll_rate > rate ? pll_rate - rate : rate - pll_rate) > rate / 1000) + { + LOG_E("VP%u HDMI clock mismatch: req %lu dclk %lu pixel %lu", + vp_id, rate, actual_rate, pll_rate); + rt_clk_put(pixel_clk); + rt_clk_put(dclk); + return -RT_EIO; + } + + LOG_D("VP%u HDMI clock: dclk=%lu pixel=%lu", vp_id, actual_rate, pll_rate); + rt_clk_put(pixel_clk); + rt_clk_put(dclk); return RT_EOK; } @@ -687,6 +902,21 @@ rt_err_t vop2fb_vp_mode_set(struct vop2fb *vfb, rt_uint32_t vp_id, return -RT_EINVAL; } + if (vfb->soc->soc == VOP2FB_SOC_RK3528) + { + if (vp_id != 0 || vfb->output_if != VOP2FB_OUTPUT_HDMI || + tim->hactive > 1920 || tim->vactive > 1080) + { + return -RT_ENOSYS; + } + /* Progressive SDR RGB: no inherited LUT, mirroring or color bars. */ + vop2fb_reg_write(vfb, base, RT_BIT(31)); + vop2fb_reg_write(vfb, base + 0x04, 0); + vop2fb_reg_write(vfb, base + 0x2c, 0); + vop2fb_reg_write(vfb, base + 0x08, 0); + vop2fb_reg_write(vfb, RK3528_VP0_ACM_CTRL, 1); + } + if (tim->pixelclock) { if ((err = vop2fb_vp_dclk_set_rate(vfb, vp_id, @@ -716,6 +946,24 @@ rt_err_t vop2fb_vp_mode_set(struct vop2fb *vfb, rt_uint32_t vp_id, out_mode = vop2fb_vp_out_mode(vfb); vop2fb_mask_write(vfb, base, VOP2_VP_OUT_MODE_MASK, VOP2_VP_OUT_MODE_SHIFT, out_mode); + + if (vfb->soc->soc == VOP2FB_SOC_RK3588 && + vfb->output_if == VOP2FB_OUTPUT_HDMI) + { + /* The RT framebuffer and the RK3588 QP link both use RGB. */ + vop2fb_mask_write(vfb, base, 0x1f, + VOP2_VP_DSP_DATA_SWAP_SHIFT, 0); + vop2fb_mask_write(vfb, base, 1, VOP2_VP_POST_DSP_OUT_R2Y_SHIFT, + 0); + vop2fb_reg_write(vfb, base + (VOP2_VP_BCSH_CTRL - VOP2_VP_DSP_CTRL), + 0); + vop2fb_reg_write(vfb, base + (VOP2_VP_BCSH_BCS - VOP2_VP_DSP_CTRL), + 0); + vop2fb_mask_write(vfb, base + (VOP2_VP_BCSH_COLOR - VOP2_VP_DSP_CTRL), + 1, VOP2_VP_BCSH_EN_SHIFT, 0); + vop2fb_reg_write(vfb, base + (VOP2_VP_DSP_BG - VOP2_VP_DSP_CTRL), + 0); + } LOG_D("VP%u out_mode=%u if=%d", vp_id, out_mode, vfb->output_if); HWREG32((rt_uint8_t *)vfb->regs + base + (VOP2_VP_DSP_HTOTAL_HS_END - VOP2_VP_DSP_CTRL)) = @@ -747,9 +995,11 @@ rt_err_t vop2fb_vp_mode_set(struct vop2fb *vfb, rt_uint32_t vp_id, else { vop2fb_rk3568_if_cfg(vfb, vp_id, tim); + vop2fb_rk3588_hdmi0_cfg(vfb, vp_id, tim); } vop2fb_rk3568_dither_setup(vfb, vp_id); + #if DBG_LVL >= DBG_LOG vop2fb_rk3568_dump_route_regs(vfb, vp_id); #endif @@ -785,7 +1035,18 @@ rt_err_t vop2fb_vp_enable(struct vop2fb *vfb, rt_uint32_t vp_id, rt_bool_t enabl } vop2fb_vp_irq_ctrl(vfb, vp_id, enable); + + if (enable && vfb->soc->soc == VOP2FB_SOC_RK3588) + { + /* Keep VP0's read arbitration enabled as Linux does for scanout. */ + vop2fb_mask_write(vfb, RK3588_SYS_VAR_FREQ_CTRL, 1, + RK3588_VP_ALMOST_FULL_OR_EN_SHIFT(vp_id), 1); + vop2fb_mask_write(vfb, RK3588_SYS_VAR_FREQ_CTRL, 1, + RK3588_VP_LINE_FLAG_OR_EN_SHIFT(vp_id), 1); + } + vop2fb_mask_write(vfb, base, 1, VOP2_VP_STANDBY_SHIFT, enable ? 0 : 1); + vop2fb_reg_write(vfb, VOP2_REG_CFG_DONE, vop2fb_cfg_done_vp_val(vfb, vp_id)); @@ -799,6 +1060,22 @@ rt_err_t vop2fb_mix_alpha_set(struct vop2fb *vfb, rt_uint32_t mix_reg, rt_uint8_ return -RT_EINVAL; } + if (vfb->soc->soc == VOP2FB_SOC_RK3528) + { + /* Combined pixel/global alpha uses the hardware's premultiply mode. */ + rt_uint32_t blend = alpha == 0xff ? 1 : 2; + rt_uint32_t factor = alpha == 0xff ? 1 : 5; + rt_uint32_t color_mode = alpha == 0xff ? 1 : 0; + + vop2fb_reg_write(vfb, mix_reg - 8, + ((rt_uint32_t)alpha << 16) | RT_BIT(8) | + (factor << 5) | (blend << 2) | color_mode); + vop2fb_reg_write(vfb, mix_reg - 4, 0x00ff0060); + vop2fb_reg_write(vfb, mix_reg, 0x20 | (blend << 2)); + vop2fb_reg_write(vfb, mix_reg + 4, 0x74); + return RT_EOK; + } + /* * Opaque primary scanout should not rewrite RK3568 mix registers during * bring-up. Clearing these registers can turn a valid timing stream into @@ -878,6 +1155,54 @@ rt_err_t vop2fb_overlay_init(struct vop2fb *vfb) return -RT_EINVAL; } + if (vfb->soc->soc == VOP2FB_SOC_RK3528) + { + rt_uint32_t i, region; + + if (!vfb->configured_vp_mask) + { + /* Quiesce inherited windows; no Cluster/AFBC source in this path. */ + vop2fb_mask_write(vfb, VOP2_CLUSTER0_BASE + VOP2_CLUSTER_WIN0_CTRL0, + 1, VOP2_WIN_EN_SHIFT, 0); + vop2fb_mask_write(vfb, VOP2_CLUSTER0_BASE + VOP2_CLUSTER_CTRL, + 1, VOP2_CLUSTER_EN_SHIFT, 0); + for (i = 0; i < 4; ++i) + { + for (region = 0; region < 4; ++region) + { + vop2fb_mask_write(vfb, VOP2_ESMART0_BASE + + i * VOP2_ESMART_STRIDE + VOP2_REGION0_CTRL + region * 0x40, + 1, VOP2_WIN_EN_SHIFT, 0); + } + } + vop2fb_reg_write(vfb, RK3588_WIN_REG_CFG_DONE, 0x00f100f1); + } + /* Four 2K line buffers, engines 0/1/2 for primary/overlay/cursor. */ + vop2fb_mask_write(vfb, 0x58, 3, 26, 3); + vop2fb_mask_write(vfb, VOP2_SYS_AXI_LUT_CTRL, 1, 16, 0); + vop2fb_reg_write(vfb, RK3528_OVL_SYS_PORT_SEL, 1U << 28); + vop2fb_reg_write(vfb, RK3528_OVL_PORT0_LAYER_SEL, 0x0321); + vop2fb_reg_write(vfb, RK3528_OVL_PORT0_CTRL, 0); + vop2fb_mask_write(vfb, 0x500, 3, 4, 0); + vop2fb_reg_write(vfb, RK3528_SDR2HDR_CTRL, RT_BIT(2)); + vop2fb_reg_write(vfb, RK3528_HDRVIVID_CTRL, RT_BIT(2)); + for (i = 0; i < 4; ++i) + { + vop2fb_reg_write(vfb, RK3528_OVL_ESMART0_DLY + i * 4, 0); + vop2fb_reg_write(vfb, 0x660 + i * 4, 0); + } + vop2fb_mask_write(vfb, RK3528_OVL_PORT0_BG_MIX, 0xffff, 0, 0); + vop2fb_mix_alpha_set(vfb, 0x628, vfb->overlays[0] ? vfb->overlays[0]->alpha : 0xff); + vop2fb_mix_alpha_set(vfb, 0x638, vfb->cursor ? vfb->cursor->alpha : 0xff); + /* Disabled Cluster0 at the final slot must pass through the lower layers. */ + vop2fb_reg_write(vfb, 0x640, 0x00ff01a0); + vop2fb_reg_write(vfb, 0x644, 0x00ff0060); + vop2fb_reg_write(vfb, 0x648, 0x20); + vop2fb_reg_write(vfb, 0x64c, 0x74); + vop2fb_reg_write(vfb, VOP2_REG_CFG_DONE, vop2fb_cfg_done_vp_val(vfb, 0)); + return RT_EOK; + } + /* RK3568 uses shared OVL_LAYER_SEL; newer VOP2 SoCs use per-VP port blocks. */ if (vfb->soc->soc == VOP2FB_SOC_RK3568) { @@ -1029,10 +1354,10 @@ rt_err_t vop2fb_overlay_init(struct vop2fb *vfb) vop2fb_collect_plane_cfg(layers, &count, RT_ARRAY_SIZE(layers), vfb->cursor); - if (!count && vfb->soc->primary) + if (!count && vfb->primary_desc) { vop2fb_layer_cfg_insert(layers, &count, RT_ARRAY_SIZE(layers), - vfb->soc->primary, 0); + vfb->primary_desc, 0); } for (i = 0; i < count; ++i) @@ -1066,6 +1391,28 @@ rt_err_t vop2fb_overlay_init(struct vop2fb *vfb) vop2fb_reg_write(vfb, port_ctrl, 1); } + else if (vfb->soc->soc == VOP2FB_SOC_RK3588) + { + /* Esmart0 is the linear RGB primary, at mixer layer 0. */ + rt_uint32_t layer_sel = 0x75643102; + rt_uint32_t port_sel = 0x00007888; + + port_sel &= ~(0xfu << (vfb->vp_id * 4)); + port_sel |= (vfb->vp_id & 3u) << 24; + + /* U-Boot and EDK2 require immediate overlay route commits on RK3588. */ + vop2fb_mask_write(vfb, VOP2_OVL_CTRL, 1, + RK3568_OVL_LAYERSEL_REGDONE_IMD_SHIFT, 1); + vop2fb_reg_write(vfb, VOP2_OVL_LAYER_SEL, layer_sel); + vop2fb_reg_write(vfb, VOP2_OVL_PORT_SEL, port_sel); + + vop2fb_mask_write(vfb, VOP2_OVL_CTRL, 0x3, + RK3588_OVL_CFG_DONE_PORT_SHIFT, vfb->vp_id); + vop2fb_mask_write(vfb, VOP2_OVL_CTRL, 1, + VOP2_OVL_MODE_SEL_SHIFT + vfb->vp_id, + 0); + + } vop2fb_reg_write(vfb, VOP2_REG_CFG_DONE, vop2fb_cfg_done_val(vfb)); @@ -1086,6 +1433,11 @@ static const struct vop2fb_win_desc *vop2fb_win_by_plane_id(struct vop2fb *vfb, return vfb->soc->cursor; } + /* + * This lookup runs during VOP probe, before the runtime overlay plane + * count is published. The device tree uses physical window IDs, so + * search the complete SoC window table just as Linux VOP2 does. + */ for (i = 0; i < vfb->soc->overlay_count; ++i) { if (vfb->soc->overlays[i].plane_id == plane_id) @@ -1101,15 +1453,16 @@ rt_err_t vop2fb_parse_port_planes(struct vop2fb *vfb, struct rt_ofw_node *vop_np rt_uint32_t vp_id) { struct rt_ofw_node *ports_np, *port_np, *child; - rt_uint32_t reg, primary_id = RK_PLANE_ESMART0, cursor_id = RK_PLANE_SMART1; - - RT_UNUSED(vop2fb_win_by_plane_id); + rt_uint32_t reg, primary_id, cursor_id; if (!vfb || !vop_np) { return -RT_EINVAL; } + primary_id = vfb->soc->primary->plane_id; + cursor_id = vfb->soc->cursor->plane_id; + ports_np = rt_ofw_get_child_by_tag(vop_np, "ports"); if (!ports_np) @@ -1133,10 +1486,30 @@ rt_err_t vop2fb_parse_port_planes(struct vop2fb *vfb, struct rt_ofw_node *vop_np return -RT_ENOENT; } - rt_ofw_prop_read_u32(port_np, "rockchip,primary-plane", &primary_id); - rt_ofw_prop_read_u32(port_np, "cursor-win-id", &cursor_id); + /* Keep the RK3588 SoC default primary chosen by the VOP data table. */ + if (vfb->soc->soc != VOP2FB_SOC_RK3588 && + vfb->soc->soc != VOP2FB_SOC_RK3528) + { + rt_ofw_prop_read_u32(port_np, "rockchip,primary-plane", &primary_id); + rt_ofw_prop_read_u32(port_np, "cursor-win-id", &cursor_id); + } + + vfb->primary_desc = vop2fb_win_by_plane_id(vfb, primary_id); + if (!vfb->primary_desc) + { + LOG_W("VP%u primary-plane=%u is unsupported, use %s", vp_id, + primary_id, vfb->soc->primary->name); + vfb->primary_desc = vfb->soc->primary; + } + + vfb->cursor_desc = vop2fb_win_by_plane_id(vfb, cursor_id); + if (!vfb->cursor_desc) + { + vfb->cursor_desc = vfb->soc->cursor; + } - LOG_D("VP%u primary-plane=%u cursor-win-id=%u", vp_id, primary_id, cursor_id); + LOG_D("VP%u primary-plane=%u -> %s, cursor-win-id=%u", vp_id, + primary_id, vfb->primary_desc->name, cursor_id); return RT_EOK; } diff --git a/bsp/rockchip/dm/graphic/framebuffer/fb-vop2-vp.h b/bsp/rockchip/dm/graphic/framebuffer/fb-vop2-vp.h index 79796cf0ebf8..b7fbe11894dd 100644 --- a/bsp/rockchip/dm/graphic/framebuffer/fb-vop2-vp.h +++ b/bsp/rockchip/dm/graphic/framebuffer/fb-vop2-vp.h @@ -17,7 +17,9 @@ #define VOP2FB_VP_STRIDE 0x100 #define VOP2_VP_DSP_CTRL 0xC00 +#define VOP2_VP_CLK_CTRL 0xC0C #define VOP2_VP_DSP_BG 0xC2C +#define VOP2_VP_BCSH_CTRL 0xC60 #define RK3568_VP0_PRE_SCAN_HTIMING 0xC30 #define RK3568_VP0_POST_DSP_HACT_INFO 0xC34 #define RK3568_VP0_POST_DSP_VACT_INFO 0xC38 @@ -28,6 +30,15 @@ #define VOP2_VP_DSP_HACT_ST_END 0xC4C #define VOP2_VP_DSP_VTOTAL_VS_END 0xC50 #define VOP2_VP_DSP_VACT_ST_END 0xC54 +#define VOP2_VP_BCSH_CTRL 0xC60 +#define VOP2_VP_BCSH_BCS 0xC64 +#define VOP2_VP_BCSH_COLOR 0xC6C + +#define VOP2_VP_BCSH_R2Y_EN_SHIFT 4 +#define VOP2_VP_BCSH_Y2R_CSC_MODE_SHIFT 2 +#define VOP2_VP_BCSH_R2Y_CSC_MODE_SHIFT 6 +#define VOP2_VP_BCSH_OUT_MODE_NORMAL (3U << 30) +#define VOP2_VP_BCSH_EN_SHIFT 31 #define VOP2_OVL_CTRL 0x600 #define VOP2_OVL_MODE_SEL_SHIFT 0 @@ -37,10 +48,12 @@ #define RK3568_BG_MIX_CTRL_MASK 0xff #define RK3568_BG_MIX_CTRL_SHIFT 24 #define RK3568_OVL_LAYERSEL_REGDONE_IMD_SHIFT 28 +#define RK3588_OVL_CFG_DONE_PORT_SHIFT 30 #define RK3568_POST_HORIZONTAL_SCALEDOWN_EN_SHIFT 0 #define RK3568_POST_VERTICAL_SCALEDOWN_EN_SHIFT 1 #define RK3568_DSP_IF_EN 0x028 +#define RK3568_DSP_IF_CTRL 0x02c #define RK3568_DSP_IF_POL 0x030 #define RK3568_VP0_INT_EN 0x0A0 @@ -52,6 +65,19 @@ #define VOP2_VP_OUT_MODE_MASK 0xf #define VOP2_VP_PRE_DITHER_DOWN_EN_SHIFT 16 #define VOP2_VP_DITHER_DOWN_EN_SHIFT 17 +#define VOP2_VP_POST_DSP_OUT_R2Y_SHIFT 15 +#define VOP2_VP_DSP_DATA_SWAP_SHIFT 8 +#define VOP2_VP_DSP_RG_SWAP_SHIFT 10 +#define VOP2_VP_DSP_RB_SWAP_SHIFT 9 +#define VOP2_VP_BCSH_R2Y_EN_SHIFT 4 +#define VOP2_VP_BCSH_R2Y_CSC_MODE_SHIFT 6 +#define VOP2_VP_GAMMA_UPDATE_EN_SHIFT 22 +#define VOP2_VP_DSP_LUT_EN_SHIFT 28 + +#define RK3588_GAMMA_LUT_LEN 1024 +#define RK3588_LUT_PORT_GAMMA_SHIFT 12 +#define RK3588_LUT_PORT_GAMMA_MASK 0x3 + #define RK3568_HDMI0_EN_SHIFT 1 #define RK3568_EDP0_EN_SHIFT 3 @@ -81,6 +107,18 @@ #define RK3568_IF_CTRL_RGB_PIN_POL_SHIFT 0 #define RK3568_IF_PIN_POL_MASK 0x7 +/* RK3588 HDMI0 uses the HDMI/eDP combo0 interface. */ +#define RK3588_HDMI0_EN_SHIFT 3 +#define RK3588_HDMI0_MUX_SHIFT 16 +#define RK3588_HDMI0_DCLK_DIV_SHIFT 16 +#define RK3588_HDMI0_PIXCLK_DIV_SHIFT 18 + +#define RK3588_GRF_VOP_CON2 0x0008 +#define RK3588_GRF_VOP_HDMI0_EN RT_BIT(1) +#define RK3588_GRF_VO1_CON0 0x0000 +#define RK3588_GRF_VO1_HDMI0_PIN_POL_SHIFT 5 +#define RK3588_GRF_VO1_HDMI_PIN_POL_MASK 0x3 + #define RK3568_HSYNC_POSITIVE RT_BIT(0) #define RK3568_VSYNC_POSITIVE RT_BIT(1) diff --git a/bsp/rockchip/dm/graphic/framebuffer/fb-vop2.c b/bsp/rockchip/dm/graphic/framebuffer/fb-vop2.c index 0203bf25a6ef..69253e9f51e7 100755 --- a/bsp/rockchip/dm/graphic/framebuffer/fb-vop2.c +++ b/bsp/rockchip/dm/graphic/framebuffer/fb-vop2.c @@ -93,7 +93,7 @@ static const struct vop2fb_win_desc rk3568_cluster1_route = .type = VOP2FB_LAYER_CLUSTER, .base_offset = VOP2_CLUSTER0_BASE + VOP2_CLUSTER_STRIDE, .layer_sel_id = 1, - .win_sel_port_offset = 1, + .win_sel_port_offset = 4, .plane_id = -1, }; @@ -112,47 +112,45 @@ static const struct vop2fb_win_desc rk3528_primary = .name = "Esmart0", .type = VOP2FB_LAYER_ESMART, .base_offset = VOP2_ESMART0_BASE, - .z_order = 2, + .z_order = 0, + .plane_id = RK_PLANE_ESMART0, + .layer_sel_id = 1, + .win_sel_port_offset = 8, + .cfg_done_bit = 4, .axi_yrgb_id = 0x06, .axi_uv_id = 0x07, }; static const struct vop2fb_win_desc rk3528_cursor = { - .name = "Esmart3", + .name = "Esmart2", .type = VOP2FB_LAYER_ESMART, - .base_offset = VOP2_ESMART0_BASE + VOP2_ESMART_STRIDE * 3, - .z_order = 5, - .axi_yrgb_id = 0x0c, - .axi_uv_id = 0x0d, + .base_offset = VOP2_ESMART0_BASE + VOP2_ESMART_STRIDE * 2, + .z_order = 2, + .plane_id = RK_PLANE_ESMART2, + .layer_sel_id = 3, + .win_sel_port_offset = 12, + .cfg_done_bit = 6, + .mix_alpha_reg = 0x638, + .axi_yrgb_id = 0x0a, + .axi_uv_id = 0x0b, }; static const struct vop2fb_win_desc rk3528_overlays[] = { - { - .name = "Cluster0", - .type = VOP2FB_LAYER_CLUSTER, - .base_offset = VOP2_CLUSTER0_BASE, - .z_order = 0, - .axi_yrgb_id = 0x02, - .axi_uv_id = 0x03, - }, { .name = "Esmart1", .type = VOP2FB_LAYER_ESMART, .base_offset = VOP2_ESMART0_BASE + VOP2_ESMART_STRIDE, - .z_order = 3, + .z_order = 1, + .plane_id = RK_PLANE_ESMART1, + .layer_sel_id = 2, + .win_sel_port_offset = 10, + .cfg_done_bit = 5, + .mix_alpha_reg = 0x628, .axi_yrgb_id = 0x08, .axi_uv_id = 0x09, }, - { - .name = "Esmart2", - .type = VOP2FB_LAYER_ESMART, - .base_offset = VOP2_ESMART0_BASE + VOP2_ESMART_STRIDE * 2, - .z_order = 4, - .axi_yrgb_id = 0x0a, - .axi_uv_id = 0x0b, - }, }; static const struct vop2fb_win_desc rk3562_primary = @@ -200,9 +198,14 @@ static const struct vop2fb_win_desc rk3588_primary = .name = "Esmart0", .type = VOP2FB_LAYER_ESMART, .base_offset = VOP2_ESMART0_BASE, - .z_order = 4, - .axi_yrgb_id = 0x10, - .axi_uv_id = 0x11, + .layer_sel_id = 2, + .win_sel_port_offset = 4, + .z_order = 0, + .axi_id = 0, + .axi_yrgb_id = 0x0a, + .axi_uv_id = 0x0b, + .cfg_done_bit = 4, + .plane_id = RK_PLANE_ESMART0, }; static const struct vop2fb_win_desc rk3588_cursor = @@ -210,44 +213,56 @@ static const struct vop2fb_win_desc rk3588_cursor = .name = "Esmart3", .type = VOP2FB_LAYER_ESMART, .base_offset = VOP2_ESMART0_BASE + VOP2_ESMART_STRIDE * 3, + .layer_sel_id = 7, + .win_sel_port_offset = 7, .z_order = 7, - .axi_yrgb_id = 0x16, - .axi_uv_id = 0x17, + .axi_id = 1, + .axi_yrgb_id = 0x0c, + .axi_uv_id = 0x0d, + .cfg_done_bit = 7, + .plane_id = RK_PLANE_ESMART3, }; static const struct vop2fb_win_desc rk3588_overlays[] = { - { - .name = "Cluster0", - .type = VOP2FB_LAYER_CLUSTER, - .base_offset = VOP2_CLUSTER0_BASE, - .z_order = 0, - .axi_yrgb_id = 0x02, - .axi_uv_id = 0x03, - }, { .name = "Cluster1", .type = VOP2FB_LAYER_CLUSTER, .base_offset = VOP2_CLUSTER0_BASE + VOP2_CLUSTER_STRIDE, + .layer_sel_id = 1, + .win_sel_port_offset = 1, .z_order = 1, - .axi_yrgb_id = 0x04, - .axi_uv_id = 0x05, + .axi_id = 0, + .axi_yrgb_id = 0x06, + .axi_uv_id = 0x07, + .cfg_done_bit = 1, + .plane_id = RK_PLANE_CLUSTER1, }, { .name = "Esmart1", .type = VOP2FB_LAYER_ESMART, .base_offset = VOP2_ESMART0_BASE + VOP2_ESMART_STRIDE, + .layer_sel_id = 3, + .win_sel_port_offset = 5, .z_order = 5, - .axi_yrgb_id = 0x12, - .axi_uv_id = 0x13, + .axi_id = 0, + .axi_yrgb_id = 0x0c, + .axi_uv_id = 0x0d, + .cfg_done_bit = 5, + .plane_id = RK_PLANE_ESMART1, }, { .name = "Esmart2", .type = VOP2FB_LAYER_ESMART, .base_offset = VOP2_ESMART0_BASE + VOP2_ESMART_STRIDE * 2, + .layer_sel_id = 6, + .win_sel_port_offset = 6, .z_order = 6, - .axi_yrgb_id = 0x14, - .axi_uv_id = 0x15, + .axi_id = 1, + .axi_yrgb_id = 0x0a, + .axi_uv_id = 0x0b, + .cfg_done_bit = 6, + .plane_id = RK_PLANE_ESMART2, }, }; @@ -461,10 +476,11 @@ static struct vop2fb_vp_state *vop2fb_output_from_graphic( static rt_uint32_t vop2fb_xvir(rt_uint32_t line_length) { /* - * Rockchip VOP2 VIR registers use a 32-bit word stride, not the older - * VOP-style 32-byte unit. Linux/U-Boot program this as pitch / 4. + * VOP2 VIR counts 32-bit fetch units, matching the Rockchip formula + * ALIGN(width * bits_per_pixel, 32) >> 5. line_length is bytes, hence + * the equivalent conversion is a four-byte alignment then divide by 4. */ - return line_length >> 2; + return RT_ALIGN(line_length, 4) >> 2; } static rt_bool_t vop2fb_mode_rb_swap(rt_uint32_t mode) @@ -495,6 +511,101 @@ static void vop2fb_regsbak_sync(struct vop2fb *vfb) return; } + if (vfb->soc->soc == VOP2FB_SOC_RK3528) + { + /* Only snapshot control registers used by the RGB VP0 path. */ + static const rt_uint16_t offsets[] = + { + 0x24, 0x28, 0x30, 0x58, 0x500, 0x504, 0x600, 0x604, + 0x670, 0xc00, 0xc2c, 0xc40, 0xcd0, 0x2010, 0x2040, + VOP2_CLUSTER0_BASE + VOP2_CLUSTER_WIN0_CTRL0, + VOP2_CLUSTER0_BASE + VOP2_CLUSTER_CTRL, + }; + rt_size_t win; + + for (i = 0; i < RT_ARRAY_SIZE(offsets); ++i) + { + vfb->regsbak[offsets[i] / 4] = + HWREG32((rt_uint8_t *)vfb->regs + offsets[i]); + } + for (win = 0; win < 4; ++win) + { + rt_uint32_t base = VOP2_ESMART0_BASE + win * VOP2_ESMART_STRIDE; + static const rt_uint8_t win_offsets[] = { 0, 4, 8, 0x10, 0x50, 0x90, 0xd0 }; + + for (i = 0; i < RT_ARRAY_SIZE(win_offsets); ++i) + { + vfb->regsbak[(base + win_offsets[i]) / 4] = + HWREG32((rt_uint8_t *)vfb->regs + base + win_offsets[i]); + } + } + return; + } + + if (vfb->soc->soc == VOP2FB_SOC_RK3588) + { + /* + * RK3588 has inactive VOP2 aperture holes (notably at 0x3c8), so a + * linear read faults. The RMW cache still must start from hardware + * reset values: mask writes otherwise clear untouched control bits. + * Snapshot only the powered, documented control registers used by + * this driver; each has been verified readable on RK3588. + */ + static const rt_uint16_t offsets[] = + { + VOP2_SYS_AXI_LUT_CTRL, + RK3588_SYS_VAR_FREQ_CTRL, + RK3568_DSP_IF_EN, + RK3568_DSP_IF_CTRL, + RK3568_DSP_IF_POL, + VOP2_OVL_CTRL, + VOP2_OVL_LAYER_SEL, + VOP2_OVL_PORT_SEL, + RK3568_VP0_BG_MIX_CTRL, + VOP2_VP_DSP_CTRL, + VOP2_VP_CLK_CTRL, + VOP2_VP_DSP_BG, + VOP2_VP_BCSH_CTRL, + VOP2_VP_BCSH_BCS, + + VOP2_CLUSTER0_BASE + VOP2_CLUSTER_WIN0_CTRL0, + VOP2_CLUSTER0_BASE + VOP2_CLUSTER_WIN0_CTRL1, + VOP2_CLUSTER0_BASE + VOP2_CLUSTER_WIN0_CTRL2, + VOP2_CLUSTER0_BASE + VOP2_CLUSTER_CTRL, + VOP2_CLUSTER0_BASE + VOP2_CLUSTER_STRIDE + VOP2_CLUSTER_WIN0_CTRL0, + VOP2_CLUSTER0_BASE + VOP2_CLUSTER_STRIDE + VOP2_CLUSTER_WIN0_CTRL1, + VOP2_CLUSTER0_BASE + VOP2_CLUSTER_STRIDE + VOP2_CLUSTER_WIN0_CTRL2, + VOP2_CLUSTER0_BASE + VOP2_CLUSTER_STRIDE + VOP2_CLUSTER_CTRL, + + VOP2_ESMART0_BASE + VOP2_ESMART_CTRL0, + VOP2_ESMART0_BASE + VOP2_ESMART_CTRL1, + VOP2_ESMART0_BASE + VOP2_ESMART_AXI_CTRL, + VOP2_ESMART0_BASE + VOP2_REGION0_CTRL, + VOP2_ESMART0_BASE + VOP2_ESMART_STRIDE + VOP2_ESMART_CTRL0, + VOP2_ESMART0_BASE + VOP2_ESMART_STRIDE + VOP2_ESMART_CTRL1, + VOP2_ESMART0_BASE + VOP2_ESMART_STRIDE + VOP2_ESMART_AXI_CTRL, + VOP2_ESMART0_BASE + VOP2_ESMART_STRIDE + VOP2_REGION0_CTRL, + VOP2_ESMART0_BASE + VOP2_ESMART_STRIDE * 2 + VOP2_ESMART_CTRL0, + VOP2_ESMART0_BASE + VOP2_ESMART_STRIDE * 2 + VOP2_ESMART_CTRL1, + VOP2_ESMART0_BASE + VOP2_ESMART_STRIDE * 2 + VOP2_ESMART_AXI_CTRL, + VOP2_ESMART0_BASE + VOP2_ESMART_STRIDE * 2 + VOP2_REGION0_CTRL, + VOP2_ESMART0_BASE + VOP2_ESMART_STRIDE * 3 + VOP2_ESMART_CTRL0, + VOP2_ESMART0_BASE + VOP2_ESMART_STRIDE * 3 + VOP2_ESMART_CTRL1, + VOP2_ESMART0_BASE + VOP2_ESMART_STRIDE * 3 + VOP2_ESMART_AXI_CTRL, + VOP2_ESMART0_BASE + VOP2_ESMART_STRIDE * 3 + VOP2_REGION0_CTRL, + }; + + for (i = 0; i < RT_ARRAY_SIZE(offsets); ++i) + { + rt_uint32_t offset = offsets[i]; + + vfb->regsbak[offset >> 2] = + HWREG32((rt_uint8_t *)vfb->regs + offset); + } + + return; + } + regs = (volatile rt_uint32_t *)vfb->regs; for (i = 0; i < vfb->soc->regbak_words; ++i) @@ -503,12 +614,79 @@ static void vop2fb_regsbak_sync(struct vop2fb *vfb) } } +static rt_err_t vop2fb_rk3588_cluster0_power_on(struct vop2fb *vfb) +{ + rt_uint32_t status; + rt_uint32_t i; + + if (!vfb || !vfb->regs || !vfb->soc || + vfb->soc->soc != VOP2FB_SOC_RK3588) + { + return RT_EOK; + } + + status = HWREG32((rt_uint8_t *)vfb->regs + RK3568_SYS_STATUS0); + if (!(status & RT_BIT(RK3588_VOP_PD_CLUSTER0_STATUS))) + { + return RT_EOK; + } + + /* A zero in PD_CTRL powers the Cluster0 window block on immediately. */ + HWREG32((rt_uint8_t *)vfb->regs + RK3568_SYS_PD_CTRL) &= + ~RT_BIT(RK3588_VOP_PD_CLUSTER0_SHIFT); + + for (i = 0; i < 50; ++i) + { + rt_hw_us_delay(1); + status = HWREG32((rt_uint8_t *)vfb->regs + RK3568_SYS_STATUS0); + if (!(status & RT_BIT(RK3588_VOP_PD_CLUSTER0_STATUS))) + { + return RT_EOK; + } + } + + LOG_E("rk3588 Cluster0 power domain did not enable (status=%08x)", status); + return -RT_EIO; +} + +static rt_err_t vop2fb_rk3588_esmart_power_on(struct vop2fb *vfb) +{ + rt_uint32_t status; + rt_uint32_t i; + + if (!vfb || !vfb->regs || !vfb->soc || + vfb->soc->soc != VOP2FB_SOC_RK3588) + { + return RT_EOK; + } + + /* Linux keeps this shared domain on to avoid display timing faults. */ + HWREG32((rt_uint8_t *)vfb->regs + RK3568_SYS_PD_CTRL) &= + ~RT_BIT(RK3588_VOP_PD_ESMART_SHIFT); + + for (i = 0; i < 50; ++i) + { + status = HWREG32((rt_uint8_t *)vfb->regs + RK3568_SYS_STATUS0); + if (!(status & RT_BIT(RK3588_VOP_PD_ESMART_STATUS))) + { + return RT_EOK; + } + rt_hw_us_delay(1); + } + + LOG_E("rk3588 Esmart power domain did not enable (status=%08x)", status); + return -RT_EIO; +} + static void vop2fb_rk3568_smart_prepare(struct vop2fb *vfb, const struct vop2fb_win_desc *desc, rt_uint32_t hw_fmt, rt_uint32_t mode) { rt_uint32_t base; - if (!vfb || !vfb->soc || !desc || vfb->soc->soc != VOP2FB_SOC_RK3568) + if (!vfb || !vfb->soc || !desc || + (vfb->soc->soc != VOP2FB_SOC_RK3528 && + vfb->soc->soc != VOP2FB_SOC_RK3568 && + vfb->soc->soc != VOP2FB_SOC_RK3588)) { return; } @@ -519,17 +697,25 @@ static void vop2fb_rk3568_smart_prepare(struct vop2fb *vfb, } base = desc->base_offset; - + if (vfb->soc->soc == VOP2FB_SOC_RK3528) + { + vop2fb_mask_write(vfb, base + VOP2_ESMART_CTRL0, 3, 12, + (base - VOP2_ESMART0_BASE) / VOP2_ESMART_STRIDE); + } /* * Force Smart/Esmart into a known-good 1:1 RGB scanout state. * The RT bring-up path previously only touched format/addr/size, so any * stale boot ROM/U-Boot scale or swap bits could keep HDMI black even * though timing and DMA were already alive. */ + vop2fb_mask_write(vfb, base + VOP2_ESMART_CTRL0, + 1, VOP2_YUV2RGB_EN_SHIFT, 0); + /* The framebuffer and HDMI-QP transport both use RGB. */ vop2fb_mask_write(vfb, base + VOP2_ESMART_CTRL0, 1, VOP2_RGB2YUV_EN_SHIFT, 0); vop2fb_mask_write(vfb, base + VOP2_ESMART_CTRL0, - VOP2_CSC_MODE_MASK, VOP2_CSC_MODE_SHIFT, 0); + VOP2_CSC_MODE_MASK, VOP2_CSC_MODE_SHIFT, + 0); vop2fb_mask_write(vfb, base + VOP2_ESMART_CTRL1, 1, VOP2_YMIRROR_EN_SHIFT, 0); vop2fb_mask_write(vfb, base + VOP2_REGION0_CTRL, @@ -643,6 +829,22 @@ static void vop2fb_win_disable(struct vop2fb *vfb, const struct vop2fb_win_desc } } +static void vop2fb_win_cfg_done(struct vop2fb *vfb, rt_uint32_t vp_id, + const struct vop2fb_win_desc *desc) +{ + vop2fb_reg_write(vfb, VOP2_REG_CFG_DONE, + vop2fb_cfg_done_vp_val(vfb, vp_id)); + + if ((vfb->soc->soc == VOP2FB_SOC_RK3588 || + vfb->soc->soc == VOP2FB_SOC_RK3528) && desc) + { + /* RK3588 keeps layer registers in a separate window shadow bank. */ + vop2fb_reg_write(vfb, RK3588_WIN_REG_CFG_DONE, + RT_BIT(desc->cfg_done_bit) | + (RT_BIT(desc->cfg_done_bit) << 16)); + } +} + static rt_uint32_t vop2fb_scale_mode(rt_uint32_t src, rt_uint32_t dst) { if (src == dst) @@ -689,7 +891,9 @@ static void vop2fb_smart_scale_setup(struct vop2fb *vfb, rt_uint32_t base, hmode, vmode, hfilter, vfilter; rt_uint32_t hfactor, vfactor, scl_ctrl; - if (!vfb->soc || vfb->soc->soc != VOP2FB_SOC_RK3568 || + if (!vfb->soc || (vfb->soc->soc != VOP2FB_SOC_RK3528 && + vfb->soc->soc != VOP2FB_SOC_RK3568 && + vfb->soc->soc != VOP2FB_SOC_RK3588) || (desc->type != VOP2FB_LAYER_ESMART && desc->type != VOP2FB_LAYER_SMART)) { @@ -731,11 +935,18 @@ static rt_err_t vop2fb_win_commit(struct vop2fb *vfb, rt_uint32_t vp_id, if (!enable) { vop2fb_win_disable(vfb, desc); - vop2fb_reg_write(vfb, VOP2_REG_CFG_DONE, - vop2fb_cfg_done_vp_val(vfb, vp_id)); + vop2fb_win_cfg_done(vfb, vp_id, desc); return RT_EOK; } + /* RK3528's VOP3 scaler needs different factors; expose 1:1 RGB first. */ + if (vfb->soc->soc == VOP2FB_SOC_RK3528 && + (vp_id != 0 || desc->type != VOP2FB_LAYER_ESMART || + src_w != dst_w || src_h != dst_h || src_w > 1920 || src_h > 1080)) + { + return -RT_ENOSYS; + } + act_info = ((src_h - 1) << 16) | ((src_w - 1) & 0xffff); dsp_info = ((dst_h - 1) << 16) | ((dst_w - 1) & 0xffff); dsp_st = (dst_y << 16) | (dst_x & 0xffff); @@ -747,6 +958,73 @@ static rt_err_t vop2fb_win_commit(struct vop2fb *vfb, rt_uint32_t vp_id, switch (desc->type) { case VOP2FB_LAYER_CLUSTER: + /* Cluster0 is the RK3588 HDMI0 linear RGB fetcher. */ + if (vfb->soc->soc == VOP2FB_SOC_RK3588) + { + if (desc == vfb->soc->primary) + { + /* + * VP0's fixed mixer has Cluster0 below Esmart0. Firmware + * can leave Esmart0 active from its previous framebuffer; + * disable it before Cluster0 becomes the linear primary. + */ + vop2fb_mask_write(vfb, VOP2_ESMART0_BASE + VOP2_REGION0_CTRL, + VOP2_EN_MASK, VOP2_WIN_EN_SHIFT, 0); + } + /* Do not inherit firmware scaler/filter state on Cluster0. */ + vop2fb_reg_write(vfb, base + VOP2_CLUSTER_WIN0_CTRL0, 0); + vop2fb_reg_write(vfb, base + VOP2_CLUSTER_WIN0_CTRL1, 0); + } + vop2fb_mask_write(vfb, base + VOP2_CLUSTER_CTRL, + VOP2_EN_MASK, VOP2_CLUSTER_AFBC_EN_SHIFT, 0); + vop2fb_mask_write(vfb, base + VOP2_CLUSTER_CTRL, + VOP2_CLUSTER_LB_MODE_MASK, VOP2_CLUSTER_LB_MODE_SHIFT, 0); + if (vfb->soc->soc == VOP2FB_SOC_RK3588) + { + /* A linear RGB framebuffer must not inherit AFBCD state. */ + vop2fb_reg_write(vfb, + base + VOP2_CLUSTER_WIN0_AFBCD_OUTPUT_CTRL, 0); + vop2fb_reg_write(vfb, + base + VOP2_CLUSTER_WIN0_AFBCD_CTRL, 0); + /* bit31 is cluster_frm_resetn_en; Linux keeps it asserted. */ + vop2fb_mask_write(vfb, base + VOP2_CLUSTER_CTRL, + 1, VOP2_CLUSTER_FRM_RESET_EN_SHIFT, 1); + /* Cluster0 reads the DMA pool directly through the VOP bypass. */ + vop2fb_mask_write(vfb, VOP2_SYS_AXI_LUT_CTRL, + 1, VOP2_SYS_MMU_BYPASS_EN_SHIFT, 1); + vop2fb_mask_write(vfb, VOP2_SYS_AXI_LUT_CTRL, + VOP2_SYS_MMU_BYPASS_ID_MASK, + VOP2_SYS_MMU_BYPASS_ID_SHIFT, 0); + vop2fb_mask_write(vfb, base + VOP2_CLUSTER_CTRL, + 1, VOP2_CLUSTER_MMU_BYPASS_SHIFT, 1); + } + /* + * Cluster windows retain scaler state across a warm boot. The + * framebuffer path is always 1:1, so program the same disabled + * scaler state Linux uses instead of inheriting firmware settings. + */ + vop2fb_reg_write(vfb, base + VOP2_CLUSTER_WIN0_SCL_FACTOR_YRGB, 0); + vop2fb_mask_write(vfb, base + VOP2_CLUSTER_WIN0_CTRL1, + 0x3, VOP2_CLUSTER_YRGB_XSCL_MODE_SHIFT, 0); + vop2fb_mask_write(vfb, base + VOP2_CLUSTER_WIN0_CTRL1, + 0x3, VOP2_CLUSTER_YRGB_YSCL_MODE_SHIFT, 0); + vop2fb_mask_write(vfb, base + VOP2_CLUSTER_WIN0_CTRL1, + 1, VOP2_CLUSTER_AVG2_SHIFT, 0); + vop2fb_mask_write(vfb, base + VOP2_CLUSTER_WIN0_CTRL1, + 1, VOP2_CLUSTER_AVG4_SHIFT, 0); + vop2fb_mask_write(vfb, base + VOP2_CLUSTER_WIN0_CTRL1, + 1, VOP2_CLUSTER_YRGB_GT2_SHIFT, 0); + vop2fb_mask_write(vfb, base + VOP2_CLUSTER_WIN0_CTRL1, + 1, VOP2_CLUSTER_YRGB_GT4_SHIFT, 0); + /* ARGB framebuffer scanout remains RGB through the HDMI QP link. */ + vop2fb_mask_write(vfb, base + VOP2_CLUSTER_WIN0_CTRL0, + 1, VOP2_CLUSTER_YUV2RGB_EN_SHIFT, 0); + vop2fb_mask_write(vfb, base + VOP2_CLUSTER_WIN0_CTRL0, + 1, VOP2_CLUSTER_RGB2YUV_EN_SHIFT, + 0); + vop2fb_mask_write(vfb, base + VOP2_CLUSTER_WIN0_CTRL0, + VOP2_CLUSTER_CSC_MODE_MASK, VOP2_CLUSTER_CSC_MODE_SHIFT, + 0); vop2fb_mask_write(vfb, base + VOP2_CLUSTER_WIN0_CTRL0, VOP2_WIN_FORMAT_MASK, VOP2_WIN_FORMAT_SHIFT, hw_fmt); vop2fb_reg_write(vfb, base + VOP2_CLUSTER_WIN0_VIR, xvir); @@ -762,6 +1040,36 @@ static rt_err_t vop2fb_win_commit(struct vop2fb *vfb, rt_uint32_t vp_id, case VOP2FB_LAYER_ESMART: case VOP2FB_LAYER_SMART: + if (vfb->soc->soc == VOP2FB_SOC_RK3588 && + desc == vfb->soc->primary) + { + /* + * RK3588 firmware can leave Cluster0 enabled. The normal + * one-display topology feeds all mixer stages into VP0, so an + * inherited Cluster0 source must not participate beside the + * Esmart0 primary framebuffer. + */ + vop2fb_mask_write(vfb, VOP2_CLUSTER0_BASE + + VOP2_CLUSTER_WIN0_CTRL0, VOP2_EN_MASK, + VOP2_WIN_EN_SHIFT, 0); + vop2fb_mask_write(vfb, VOP2_CLUSTER0_BASE + + VOP2_CLUSTER_CTRL, VOP2_EN_MASK, + VOP2_CLUSTER_EN_SHIFT, 0); + vop2fb_reg_write(vfb, RK3588_WIN_REG_CFG_DONE, + RT_BIT(0) | RT_BIT(16)); + } + if (vfb->soc->soc == VOP2FB_SOC_RK3588) + { + /* RT-Thread hands VOP physical scanout addresses directly. */ + vop2fb_mask_write(vfb, VOP2_SYS_AXI_LUT_CTRL, + 1, VOP2_SYS_MMU_BYPASS_EN_SHIFT, 1); + vop2fb_mask_write(vfb, VOP2_SYS_AXI_LUT_CTRL, + VOP2_SYS_MMU_BYPASS_ID_MASK, + VOP2_SYS_MMU_BYPASS_ID_SHIFT, 0); + /* ESmart0 uses RID 0, which the global RID threshold excludes. */ + vop2fb_mask_write(vfb, base + VOP2_ESMART_AXI_CTRL, + 1, VOP2_ESMART_MMU_BYPASS_SHIFT, 1); + } vop2fb_mask_write(vfb, base + VOP2_REGION0_CTRL, VOP2_WIN_FORMAT_MASK, VOP2_WIN_FORMAT_SHIFT, hw_fmt); vop2fb_reg_write(vfb, base + VOP2_REGION0_VIR, xvir); @@ -774,8 +1082,7 @@ static rt_err_t vop2fb_win_commit(struct vop2fb *vfb, rt_uint32_t vp_id, break; } - vop2fb_reg_write(vfb, VOP2_REG_CFG_DONE, - vop2fb_cfg_done_vp_val(vfb, vp_id)); + vop2fb_win_cfg_done(vfb, vp_id, desc); vop2fb_rk3568_dump_win_regs(vfb, desc); return RT_EOK; @@ -839,8 +1146,7 @@ static rt_err_t vop2fb_plane_commit(struct rt_graphic_plane *plane, rt_bool_t en priv->enabled = RT_FALSE; rt_spin_lock(&vfb->reg_lock); vop2fb_win_disable(vfb, priv->desc); - vop2fb_reg_write(vfb, VOP2_REG_CFG_DONE, - vop2fb_cfg_done_vp_val(vfb, priv->vp_id)); + vop2fb_win_cfg_done(vfb, priv->vp_id, priv->desc); rt_spin_unlock(&vfb->reg_lock); return RT_EOK; @@ -852,22 +1158,14 @@ static rt_err_t vop2fb_plane_commit(struct rt_graphic_plane *plane, rt_bool_t en return RT_EOK; } - if (priv->dma_flags & RT_DMA_F_NOCACHE) - { - dma = priv->dma_handle + priv->pan_offset; - } - else + /* Obtain the device-visible address, including any dma-ranges mapping. */ + err = rt_dma_sync_out_data(vfb->dev, + plane->framebuffer + priv->pan_offset, plane->screen_len, + &dma, priv->dma_flags); + if (err) { - /* - * RK3568 VOP is non-coherent. The generic DMA fallback currently - * treats a non-coherent device as if its allocation were uncached, - * so sync_out_data() can skip the writeback for this cached LINEAR - * framebuffer. Flush it explicitly before the continuously running - * scanout engine reads it. - */ - rt_hw_cpu_dcache_ops(RT_HW_CACHE_FLUSH, - plane->framebuffer + priv->pan_offset, plane->screen_len); - dma = priv->dma_handle + priv->pan_offset; + LOG_E("%s DMA sync failed: %s", plane->name, rt_strerror(err)); + return err; } xvir = vop2fb_xvir(plane->line_length); @@ -878,6 +1176,7 @@ static rt_err_t vop2fb_plane_commit(struct rt_graphic_plane *plane, rt_bool_t en dst_y = 0; dst_w = plane->width; dst_h = plane->height; + } else { @@ -912,32 +1211,6 @@ static rt_err_t vop2fb_plane_commit(struct rt_graphic_plane *plane, rt_bool_t en return err; } -static void vop2fb_fill_primary_background(struct rt_graphic_plane *plane) -{ - rt_uint8_t *fb; - rt_size_t pixels, i; - - if (!plane || !plane->framebuffer || !plane->framebuffer_len) - { - return; - } - - fb = plane->framebuffer; - - if (plane->bits_per_pixel != 32) - { - rt_memset(fb, 0, plane->framebuffer_len); - return; - } - - pixels = plane->framebuffer_len / sizeof(rt_uint32_t); - - for (i = 0; i < pixels; ++i) - { - ((rt_uint32_t *)fb)[i] = 0xff000000; - } -} - static void vop2fb_plane_free_fb(struct rt_graphic_plane *plane) { struct vop2fb *vfb = vop2fb_from_plane(plane); @@ -966,8 +1239,6 @@ static rt_err_t vop2fb_plane_update(struct rt_graphic_plane *plane, { struct vop2fb_plane_priv *priv = vop2fb_plane_priv(plane); - RT_UNUSED(rect); - if (priv) { priv->enabled = RT_TRUE; @@ -1002,7 +1273,11 @@ static rt_err_t vop2fb_plane_fb_remap(struct rt_graphic_plane *plane, if (plane->type == RT_GRAPHIC_PLANE_TYPE_PRIMARY) { fb_len = screen_len * 2; - dma_flags = RT_DMA_F_LINEAR; + /* + * VOP2 scanout uses the DMA pool's Device mapping. The low coherent + * pool has no normal cached linear alias on this BSP. + */ + dma_flags = RT_DMA_F_NOCACHE | RT_DMA_F_LINEAR; } else { @@ -1037,11 +1312,6 @@ static rt_err_t vop2fb_plane_fb_remap(struct rt_graphic_plane *plane, priv->dma_flags = dma_flags; priv->pan_offset = 0; - if (plane->type == RT_GRAPHIC_PLANE_TYPE_PRIMARY) - { - vop2fb_fill_primary_background(plane); - } - if (vfb->powered && plane->type == RT_GRAPHIC_PLANE_TYPE_PRIMARY) { err = vop2fb_plane_commit(plane, RT_TRUE); @@ -1247,6 +1517,31 @@ static rt_err_t vop2fb_clk_enable(struct vop2fb *vfb) return err; } + if (vfb->soc->soc == VOP2FB_SOC_RK3588) + { + struct rt_clk *root = rt_clk_get_by_name(vfb->dev, "aclk_vop_root"); + + /* aclk_vop has only a mux/gate; its root owns the divider. */ + if (rt_is_err_or_null(root)) + { + rt_clk_disable_unprepare(hclk); + return -RT_ENOENT; + } + + err = rt_clk_set_rate(root, 396000000); + if (!err) + { + err = rt_clk_set_parent(aclk, root); + } + rt_clk_put(root); + if (err) + { + LOG_E("VOP ACLK setup failed: %s", rt_strerror(err)); + rt_clk_disable_unprepare(hclk); + return err; + } + } + if ((err = rt_clk_prepare_enable(aclk))) { rt_clk_disable_unprepare(hclk); @@ -1294,6 +1589,25 @@ static void vop2fb_clk_disable(struct vop2fb *vfb) vfb->clk_enabled = RT_FALSE; } +static rt_err_t vop2fb_reset_controller(struct vop2fb *vfb) +{ + rt_err_t err; + + if (!vfb || rt_is_err_or_null(vfb->rsts)) + { + return RT_EOK; + } + + if ((err = rt_reset_control_assert(vfb->rsts))) + { + return err; + } + + rt_hw_us_delay(10); + + return rt_reset_control_deassert(vfb->rsts); +} + static struct rt_clk *vop2fb_vp_clk_find(struct vop2fb *vfb, rt_uint32_t vp_id) { @@ -1397,6 +1711,11 @@ static void vop2fb_output_wins_disable(struct vop2fb_vp_state *state) for (i = 0; i < vfb->overlay_count; ++i) { + if (&vfb->soc->overlays[i] == state->scanout) + { + continue; + } + if (vfb->overlays[i]) { vop2fb_plane_commit(vfb->overlays[i], RT_FALSE); @@ -1564,14 +1883,16 @@ static const struct rt_graphic_device_ops vop2fb_ops = .wait_vsync = vop2fb_wait_vsync, }; -static void vop2fb_rk3568_irqs_quiesce(struct vop2fb *vfb) +static void vop2fb_irqs_quiesce(struct vop2fb *vfb) { rt_uint32_t vp_id; const rt_uint32_t vp_irqs = 0xffff; const rt_uint32_t sys_irqs = 0xfe; if (!vfb || !vfb->regs || !vfb->soc || - vfb->soc->soc != VOP2FB_SOC_RK3568 || !vfb->clk_enabled) + (vfb->soc->soc != VOP2FB_SOC_RK3528 && + vfb->soc->soc != VOP2FB_SOC_RK3568 && + vfb->soc->soc != VOP2FB_SOC_RK3588) || !vfb->clk_enabled) { return; } @@ -1593,9 +1914,12 @@ static void vop2fb_rk3568_irqs_quiesce(struct vop2fb *vfb) HWREG32((rt_uint8_t *)vfb->regs + RK3568_SYS0_INT_EN) = sys_irqs << 16; HWREG32((rt_uint8_t *)vfb->regs + RK3568_SYS0_INT_CLR) = (sys_irqs << 16) | sys_irqs; - HWREG32((rt_uint8_t *)vfb->regs + RK3568_SYS1_INT_EN) = sys_irqs << 16; - HWREG32((rt_uint8_t *)vfb->regs + RK3568_SYS1_INT_CLR) = - (sys_irqs << 16) | sys_irqs; + if (vfb->soc->soc != VOP2FB_SOC_RK3528) + { + HWREG32((rt_uint8_t *)vfb->regs + RK3568_SYS1_INT_EN) = sys_irqs << 16; + HWREG32((rt_uint8_t *)vfb->regs + RK3568_SYS1_INT_CLR) = + (sys_irqs << 16) | sys_irqs; + } } static void vop2fb_isr(int irqno, void *param) @@ -1614,8 +1938,12 @@ static void vop2fb_isr(int irqno, void *param) sys0_irqs = HWREG32((rt_uint8_t *)vfb->regs + RK3568_SYS0_INT_STATUS) & RK3568_SYS_BUS_ERROR_INTR; - sys1_irqs = HWREG32((rt_uint8_t *)vfb->regs + RK3568_SYS1_INT_STATUS) & - RK3568_SYS_BUS_ERROR_INTR; + sys1_irqs = 0; + if (vfb->soc->soc != VOP2FB_SOC_RK3528) + { + sys1_irqs = HWREG32((rt_uint8_t *)vfb->regs + RK3568_SYS1_INT_STATUS) & + RK3568_SYS_BUS_ERROR_INTR; + } vp_mask = vfb->configured_vp_mask | vfb->active_vp_mask; for (vp_id = 0; vp_id < vfb->soc->vp_count && @@ -1691,6 +2019,8 @@ static void vop2fb_isr(int irqno, void *param) continue; } + vfb->frame_start_count[vp_id]++; + if (rt_atomic_exchange(&vfb->vp[vp_id].vsync, 1) == 0) { rt_sem_release(&vfb->vp[vp_id].vsync_sem); @@ -1973,7 +2303,7 @@ static rt_err_t vop2fb_cursor_plane_create(struct vop2fb *vfb, vfb->cursor = plane; - return vop2fb_plane_init(vfb, state, plane, vfb->soc->cursor, width, height, + return vop2fb_plane_init(vfb, state, plane, vfb->cursor_desc, width, height, RT_GRAPHIC_PLANE_TYPE_CURSOR); } @@ -2039,7 +2369,7 @@ static rt_err_t vop2fb_crtc_mode_set(void *priv, if (primary_output) { - state->scanout = vfb->soc->primary; + state->scanout = vfb->primary_desc; state->graphic = &vfb->parent; } else @@ -2428,6 +2758,12 @@ static rt_err_t vop2fb_parse_display(struct vop2fb *vfb, struct rt_platform_devi vfb->display_h = 1080; vfb->vp_id = 0; + if (vfb->soc && vfb->soc->soc == VOP2FB_SOC_RK3588) + { + vfb->display_w = 2560; + vfb->display_h = 1440; + } + rt_dm_dev_prop_read_u32(dev, "rockchip,fb-width", &vfb->display_w); rt_dm_dev_prop_read_u32(dev, "rockchip,fb-height", &vfb->display_h); rt_dm_dev_prop_read_u32(dev, "rockchip,active-vp", &vfb->vp_id); @@ -2445,7 +2781,7 @@ static rt_err_t vop2fb_parse_display(struct vop2fb *vfb, struct rt_platform_devi return RT_EOK; } -static rt_bool_t vop2fb_route_is_legacy_hdmi(struct rt_ofw_node *connector_np) +static rt_bool_t vop2fb_route_is_hdmi(struct rt_ofw_node *connector_np) { if (!connector_np) { @@ -2453,7 +2789,8 @@ static rt_bool_t vop2fb_route_is_legacy_hdmi(struct rt_ofw_node *connector_np) } return rt_ofw_node_is_compatible(connector_np, "rockchip,rk3568-dw-hdmi") || - rt_ofw_node_is_compatible(connector_np, "rockchip,rk3528-dw-hdmi"); + rt_ofw_node_is_compatible(connector_np, "rockchip,rk3528-dw-hdmi") || + rt_ofw_node_is_compatible(connector_np, "rockchip,rk3588-dw-hdmi"); } #if DBG_LVL >= DBG_LOG @@ -2488,7 +2825,7 @@ static enum vop2fb_output_if vop2fb_output_from_connector(struct rt_ofw_node *co return VOP2FB_OUTPUT_NONE; } - if (vop2fb_route_is_legacy_hdmi(connector_np)) + if (vop2fb_route_is_hdmi(connector_np)) { return VOP2FB_OUTPUT_HDMI; } @@ -2545,6 +2882,9 @@ static rt_err_t vop2fb_probe(struct rt_platform_device *pdev) goto _fail; } + vfb->primary_desc = vfb->soc->primary; + vfb->cursor_desc = vfb->soc->cursor; + if ((err = vop2fb_parse_display(vfb, pdev))) { goto _fail; @@ -2558,6 +2898,18 @@ static rt_err_t vop2fb_probe(struct rt_platform_device *pdev) goto _fail; } + if (vfb->soc->soc == VOP2FB_SOC_RK3588) + { + /* The gamma RAM is a distinct 4 KiB VOP resource on RK3588. */ + vfb->gamma_lut = rt_ofw_iomap_by_name(dev->ofw_node, "gamma_lut"); + if (!vfb->gamma_lut) + { + LOG_E("rk3588 gamma LUT resource is unavailable"); + err = -RT_EIO; + goto _fail_unmap; + } + } + vfb->regsbak = rt_calloc(vfb->soc->regbak_words, sizeof(rt_uint32_t)); if (!vfb->regsbak) @@ -2574,6 +2926,26 @@ static rt_err_t vop2fb_probe(struct rt_platform_device *pdev) goto _fail_regsbak; } + vfb->rsts = rt_reset_control_get_array(dev); + if (rt_is_err(vfb->rsts)) + { + LOG_W("vop2: resets unavailable: %s", rt_strerror(rt_ptr_err(vfb->rsts))); + vfb->rsts = RT_NULL; + } + + if (vfb->soc->soc == VOP2FB_SOC_RK3588) + { + vfb->vop_grf = rt_syscon_find_by_ofw_phandle(dev->ofw_node, + "rockchip,vop-grf"); + vfb->vo1_grf = rt_syscon_find_by_ofw_phandle(dev->ofw_node, + "rockchip,vo1-grf"); + + if (!vfb->vop_grf || !vfb->vo1_grf) + { + LOG_W("rk3588 HDMI output GRF is unavailable"); + } + } + vfb->irq = vop2fb_request_irq(vfb, dev); rt_spin_lock_init(&vfb->reg_lock); @@ -2585,9 +2957,24 @@ static rt_err_t vop2fb_probe(struct rt_platform_device *pdev) goto _fail_clks; } + if ((err = vop2fb_reset_controller(vfb))) + { + LOG_E("vop2: reset controller failed: %s", rt_strerror(err)); + goto _fail_clk; + } + vfb->powered = RT_TRUE; + if ((err = vop2fb_rk3588_cluster0_power_on(vfb))) + { + goto _fail_clk; + } + if ((err = vop2fb_rk3588_esmart_power_on(vfb))) + { + goto _fail_clk; + } + vop2fb_regsbak_sync(vfb); - vop2fb_rk3568_irqs_quiesce(vfb); + vop2fb_irqs_quiesce(vfb); vop2fb_axi_init_all(vfb); vop2fb_overlay_init(vfb); @@ -2650,8 +3037,8 @@ static rt_err_t vop2fb_probe(struct rt_platform_device *pdev) LOG_I("VOP2 %s: max %ux%u, primary=%s, %u overlays, cursor=%s, per-VP fb on route complete", vfb->soc->version, vfb->display_w, vfb->display_h, - vfb->soc->primary->name, (unsigned)vfb->overlay_count, - vfb->soc->cursor->name); + vfb->primary_desc->name, (unsigned)vfb->overlay_count, + vfb->cursor_desc->name); return RT_EOK; @@ -2671,9 +3058,13 @@ static rt_err_t vop2fb_probe(struct rt_platform_device *pdev) vfb->vp[vp_id].ipc_ready = RT_FALSE; } } - vop2fb_rk3568_irqs_quiesce(vfb); + vop2fb_irqs_quiesce(vfb); vop2fb_clk_disable(vfb); _fail_clks: + if (!rt_is_err_or_null(vfb->rsts)) + { + rt_reset_control_put(vfb->rsts); + } if (!rt_is_err_or_null(vfb->clks)) { rt_clk_array_put(vfb->clks); @@ -2681,6 +3072,10 @@ static rt_err_t vop2fb_probe(struct rt_platform_device *pdev) _fail_regsbak: rt_free(vfb->regsbak); _fail_unmap: + if (vfb->gamma_lut) + { + rt_iounmap(vfb->gamma_lut); + } if (vfb->regs) { rt_iounmap(vfb->regs); @@ -2725,8 +3120,7 @@ static rt_err_t vop2fb_remove(struct rt_platform_device *pdev) } } - vop2fb_rk3568_irqs_quiesce(vfb); - + vop2fb_irqs_quiesce(vfb); for (vp_id = 0; vp_id < vfb->soc->vp_count && vp_id < VOP2FB_VP_MAX; ++vp_id) { @@ -2734,11 +3128,21 @@ static rt_err_t vop2fb_remove(struct rt_platform_device *pdev) } vop2fb_clk_disable(vfb); + if (!rt_is_err_or_null(vfb->rsts)) + { + rt_reset_control_put(vfb->rsts); + } + if (!rt_is_err_or_null(vfb->clks)) { rt_clk_array_put(vfb->clks); } + if (vfb->gamma_lut) + { + rt_iounmap(vfb->gamma_lut); + } + rt_iounmap(vfb->regs); rt_free(vfb->regsbak); rt_free(vfb); diff --git a/bsp/rockchip/dm/graphic/framebuffer/fb-vop2.h b/bsp/rockchip/dm/graphic/framebuffer/fb-vop2.h index 10bc6eb3de7b..1f7df0f9b5d2 100644 --- a/bsp/rockchip/dm/graphic/framebuffer/fb-vop2.h +++ b/bsp/rockchip/dm/graphic/framebuffer/fb-vop2.h @@ -20,47 +20,87 @@ #define VOP2FB_VP_MAX 4 /* System */ -#define VOP2_REG_CFG_DONE 0x000 -#define VOP2_CFG_DONE_EN RT_BIT(15) - -#define VOP2_EN_MASK 1 +#define VOP2_REG_CFG_DONE 0x000 +#define RK3588_WIN_REG_CFG_DONE 0x00c +#define RK3588_SYS_VAR_FREQ_CTRL 0x038 +#define RK3568_SYS_PD_CTRL 0x034 +#define RK3568_SYS_STATUS0 0x060 +#define VOP2_SYS_AXI_LUT_CTRL 0x024 +#define VOP2_SYS_MMU_BYPASS_EN_SHIFT 31 +#define VOP2_SYS_MMU_BYPASS_ID_SHIFT 26 +#define VOP2_SYS_MMU_BYPASS_ID_MASK 0x1f +#define RK3588_VP_ALMOST_FULL_OR_EN_SHIFT(vp) (28 + (vp)) +#define RK3588_VP_LINE_FLAG_OR_EN_SHIFT(vp) (20 + (vp)) + +/* RK3588 VOP internal power-domain control/status bits. */ +#define RK3588_VOP_PD_CLUSTER0_SHIFT 0 +#define RK3588_VOP_PD_CLUSTER0_STATUS 8 +#define RK3588_VOP_PD_ESMART_SHIFT 7 +#define RK3588_VOP_PD_ESMART_STATUS 15 +#define VOP2_CFG_DONE_EN RT_BIT(15) + +#define VOP2_EN_MASK 1 /* Cluster layer (RK3568 layout) */ -#define VOP2_CLUSTER0_BASE 0x1000 -#define VOP2_CLUSTER_STRIDE 0x200 - -#define VOP2_CLUSTER_WIN0_CTRL0 0x00 -#define VOP2_CLUSTER_WIN0_CTRL2 0x08 -#define VOP2_CLUSTER_WIN0_YRGB_MST 0x10 -#define VOP2_CLUSTER_WIN0_VIR 0x18 -#define VOP2_CLUSTER_WIN0_ACT_INFO 0x20 -#define VOP2_CLUSTER_WIN0_DSP_INFO 0x24 -#define VOP2_CLUSTER_WIN0_DSP_ST 0x28 -#define VOP2_CLUSTER_CTRL 0x100 -#define VOP2_CLUSTER_EN_SHIFT 0 - -#define VOP2_CLUSTER_AXI_ID_SHIFT 13 -#define VOP2_CLUSTER_AXI_YRGB_ID_SHIFT 0 -#define VOP2_CLUSTER_AXI_UV_ID_SHIFT 5 -#define VOP2_CLUSTER_AXI_YRGB_ID_MASK 0x1f -#define VOP2_CLUSTER_AXI_UV_ID_MASK 0x1f -#define VOP2_CLUSTER_AXI_ID_MASK 0x1 +#define VOP2_CLUSTER0_BASE 0x1000 +#define VOP2_CLUSTER_STRIDE 0x200 + +#define VOP2_CLUSTER_WIN0_CTRL0 0x00 +#define VOP2_CLUSTER_WIN0_CTRL1 0x04 +#define VOP2_CLUSTER_WIN0_CTRL2 0x08 +#define VOP2_CLUSTER_WIN0_YRGB_MST 0x10 +#define VOP2_CLUSTER_WIN0_VIR 0x18 +#define VOP2_CLUSTER_WIN0_ACT_INFO 0x20 +#define VOP2_CLUSTER_WIN0_DSP_INFO 0x24 +#define VOP2_CLUSTER_WIN0_DSP_ST 0x28 +#define VOP2_CLUSTER_WIN0_SCL_FACTOR_YRGB 0x30 +#define VOP2_CLUSTER_WIN0_AFBCD_OUTPUT_CTRL 0x50 +#define VOP2_CLUSTER_WIN0_AFBCD_CTRL 0x6c +#define VOP2_CLUSTER_CTRL 0x100 +#define VOP2_CLUSTER_EN_SHIFT 0 +#define VOP2_CLUSTER_AFBC_EN_SHIFT 1 +#define VOP2_CLUSTER_LB_MODE_SHIFT 4 +#define VOP2_CLUSTER_LB_MODE_MASK 0xf +#define VOP2_CLUSTER_MMU_BYPASS_SHIFT 14 +#define VOP2_CLUSTER_FRM_RESET_EN_SHIFT 31 +#define VOP2_CLUSTER_AFBCD_GATING_EN_SHIFT 4 +#define VOP2_CLUSTER_AFBCD_HALF_BLOCK_SHIFT 7 +#define VOP2_CLUSTER_AFBCD_BLOCK_SPLIT_SHIFT 8 +#define VOP2_CLUSTER_YUV2RGB_EN_SHIFT 8 +#define VOP2_CLUSTER_RGB2YUV_EN_SHIFT 9 +#define VOP2_CLUSTER_CSC_MODE_SHIFT 10 +#define VOP2_CLUSTER_CSC_MODE_MASK 0x3 +#define VOP2_CLUSTER_AXI_ID_SHIFT 13 +#define VOP2_CLUSTER_AXI_YRGB_ID_SHIFT 0 +#define VOP2_CLUSTER_AXI_UV_ID_SHIFT 5 +#define VOP2_CLUSTER_AXI_YRGB_ID_MASK 0x1f +#define VOP2_CLUSTER_AXI_UV_ID_MASK 0x1f +#define VOP2_CLUSTER_AXI_ID_MASK 0x1 +#define VOP2_CLUSTER_YRGB_XSCL_MODE_SHIFT 12 +#define VOP2_CLUSTER_YRGB_YSCL_MODE_SHIFT 14 +#define VOP2_CLUSTER_AVG2_SHIFT 18 +#define VOP2_CLUSTER_AVG4_SHIFT 19 +#define VOP2_CLUSTER_YRGB_GT2_SHIFT 28 +#define VOP2_CLUSTER_YRGB_GT4_SHIFT 29 /* Esmart / Smart shared region layout */ #define VOP2_ESMART0_BASE 0x1800 #define VOP2_SMART0_BASE 0x1C00 #define VOP2_ESMART_STRIDE 0x200 #define VOP2_SMART_STRIDE 0x200 +#define VOP2_SMART_DLY_NUM 0x6f8 #define VOP2_ESMART_CTRL0 0x00 #define VOP2_ESMART_AXI_CTRL 0x08 #define VOP2_ESMART_CTRL1 0x04 +#define VOP2_ESMART_MMU_BYPASS_SHIFT 2 #define VOP2_ESMART_AXI_ID_SHIFT 1 -#define VOP2_ESMART_AXI_YRGB_ID_SHIFT 0 +#define VOP2_ESMART_AXI_YRGB_ID_SHIFT 4 #define VOP2_ESMART_AXI_UV_ID_SHIFT 12 #define VOP2_ESMART_AXI_ID_MASK 0x1 #define VOP2_ESMART_AXI_YRGB_ID_MASK 0x1f #define VOP2_ESMART_AXI_UV_ID_MASK 0x1f +#define VOP2_YUV2RGB_EN_SHIFT 0 #define VOP2_RGB2YUV_EN_SHIFT 1 #define VOP2_CSC_MODE_SHIFT 2 #define VOP2_CSC_MODE_MASK 0x3 @@ -131,6 +171,7 @@ struct vop2fb_win_desc rt_uint8_t axi_id; rt_uint8_t axi_yrgb_id; rt_uint8_t axi_uv_id; + rt_uint8_t cfg_done_bit; rt_int8_t plane_id; rt_uint32_t mix_alpha_reg; }; @@ -191,8 +232,11 @@ struct vop2fb struct rt_device *dev; const struct vop2fb_soc_data *soc; + const struct vop2fb_win_desc *primary_desc; + const struct vop2fb_win_desc *cursor_desc; void *regs; + void *gamma_lut; rt_uint32_t *regsbak; rt_uint32_t display_w; @@ -210,9 +254,13 @@ struct vop2fb rt_bool_t clk_enabled; struct rt_clk_array *clks; + struct rt_reset_control *rsts; + struct rt_syscon *vop_grf; + struct rt_syscon *vo1_grf; struct rt_spinlock reg_lock; int irq; + rt_uint32_t frame_start_count[VOP2FB_VP_MAX]; rt_uint32_t post_buf_empty_count[VOP2FB_VP_MAX]; rt_uint32_t axi_bus_error_count[2]; @@ -273,11 +321,26 @@ rt_inline rt_uint32_t vop2fb_cfg_done_pending(struct vop2fb *vfb) rt_inline rt_uint32_t vop2fb_cfg_done_vp_val(struct vop2fb *vfb, rt_uint32_t vp) { + if (vfb && vfb->soc && vfb->soc->soc == VOP2FB_SOC_RK3528) + { + rt_uint32_t mask = RT_BIT(vp) | RT_BIT(vp + 4); + return VOP2_CFG_DONE_EN | mask | (mask << 16); + } if (vfb && vfb->soc && vfb->soc->soc == VOP2FB_SOC_RK3568) { return VOP2_CFG_DONE_EN | RT_BIT(vp) | vop2fb_cfg_done_pending(vfb); } + if (vfb && vfb->soc && vfb->soc->soc == VOP2FB_SOC_RK3588) + { + /* + * REG_CFG_DONE uses a hiword write mask. The VP's done bit and + * global-regdone are sufficient for a normal RK3588 plane commit; + * the system shadow-bank bits are not part of this transaction. + */ + return VOP2_CFG_DONE_EN | RT_BIT(vp) | (RT_BIT(vp) << 16); + } + return VOP2_CFG_DONE_EN | RT_BIT(vp) | (RT_BIT(vp) << 16); } @@ -289,6 +352,11 @@ rt_inline rt_uint32_t vop2fb_cfg_done_val(struct vop2fb *vfb) rt_inline rt_uint32_t vop2fb_cfg_done_mask_val(struct vop2fb *vfb, rt_uint32_t vp_mask) { + if (vfb && vfb->soc && vfb->soc->soc == VOP2FB_SOC_RK3528) + { + vp_mask |= vp_mask << 4; + return VOP2_CFG_DONE_EN | vp_mask | (vp_mask << 16); + } if (vfb && vfb->soc && vfb->soc->soc == VOP2FB_SOC_RK3568) { return VOP2_CFG_DONE_EN | vp_mask | vop2fb_cfg_done_pending(vfb); diff --git a/bsp/rockchip/dm/graphic/framebuffer/hdmi-rk3588.c b/bsp/rockchip/dm/graphic/framebuffer/hdmi-rk3588.c new file mode 100644 index 000000000000..db620287535b --- /dev/null +++ b/bsp/rockchip/dm/graphic/framebuffer/hdmi-rk3588.c @@ -0,0 +1,1063 @@ +/* + * Copyright (c) 2006-2026, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2026-3-15 GuEe-GUI the first version + */ + +#include "rockchip-display.h" +#include "rockchip-hdmi.h" + +#define DBG_TAG "hdmi.rk3588" +#define DBG_LVL DBG_INFO +#include + +#define RK3588_GRF_SOC_CON2 0x0308 +#define RK3588_GRF_SOC_CON7 0x031c +#define RK3588_GRF_SOC_STATUS1 0x0384 +#define RK3588_HDMI0_HPD_INT_MSK RT_BIT(13) +#define RK3588_HDMI1_HPD_INT_MSK RT_BIT(15) +#define RK3588_HDMI0_LEVEL_INT RT_BIT(16) +#define RK3588_HDMI1_LEVEL_INT RT_BIT(24) +#define RK3588_SET_HPD_PATH_MASK (0x3U << 12) + +#define RK3588_GRF_VO1_CON3 0x000c +#define RK3588_GRF_VO1_CON4 0x0010 +#define RK3588_GRF_VO1_CON6 0x0018 +#define RK3588_GRF_VO1_CON7 0x001c +#define RK3588_GRF_VO1_CON9 0x0024 +#define RK3588_HDMI21_MASK RT_BIT(0) +#define RK3588_COMPRESS_MODE_MASK RT_BIT(12) +#define RK3588_COLOR_FORMAT_MASK 0xfU +#define RK3588_COLOR_DEPTH_MASK (0xfU << 4) +#define RK3588_8BPC 0U +#define RK3588_RGB 0U +#define RK3588_YUV444 2U +#define RK3588_SCLIN RT_BIT(9) +#define RK3588_SDAIN RT_BIT(10) +#define RK3588_MODE RT_BIT(11) +#define RK3588_I2S_SEL RT_BIT(13) +#define RK3588_HDMI0_GRANT_SEL RT_BIT(10) +#define RK3588_HDMI1_GRANT_SEL RT_BIT(12) + +#define TIMER_BASE_CONFIG0 0x0080 +#define I2CM_FM_SCL_CONFIG0 0x00e4 +#define I2CM_CONTROL0 0x00ec +#define I2CM_INTERFACE_CONTROL0 0x00f4 +#define I2CM_FM_EN RT_BIT(0) +#define HDCP2LOGIC_CONFIG0 0x08e0 +#define HDCP2_BYPASS RT_BIT(0) +#define FRAME_COMPOSER_CONFIG9 0x0864 +#define VIDEO_INTERFACE_CONTROL0 0x080c +#define VIDEO_INTERFACE_CONFIG0 0x0800 +#define VIDEO_INTERFACE_FIFO_CLEAR RT_BIT(0) +#define VIDEO_INTERFACE_ENABLE RT_BIT(21) +#define LINK_CONFIG0 0x0968 +#define OPMODE_DVI RT_BIT(4) +#define OPMODE_FRL RT_BIT(0) +#define MAINUNIT_0_INT_MASK_N 0x3014 +#define MAINUNIT_1_INT_MASK_N 0x3024 +#define MAINUNIT_1_INT_CLEAR 0x3028 +#define AVP_1_INT_MASK_N 0x3824 +#define AVP_3_INT_MASK_N 0x3844 +#define I2CM_NACK_RCVD_CLEAR RT_BIT(2) +#define I2CM_OP_DONE_CLEAR RT_BIT(0) + +#define RK3588_HDMI_TMDS_MAX_RATE 594000000ULL + +/* VOP owns dclk_vp*, while HDMI owns these controller clocks. */ +#define RK3588_HDMI_CONTROLLER_CLK_COUNT 6 + +#define GLOBAL_SWRESET_REQUEST 0x0040 +#define AVP_VIDEO_SWINIT RT_BIT(6) +#define AVP_AUDIO_SWINIT RT_BIT(10) +#define GLOBAL_SWDISABLE 0x0044 +#define AVP_VIDEO_SWDISABLE RT_BIT(6) +#define AVP_AUDIO_SWDISABLE RT_BIT(10) +#define CMU_STATUS 0x00b0 +#define MAINUNIT_STATUS0 0x0180 +#define VIDEO_INTERFACE_STATUS0 0x0814 +#define FRAME_COMPOSER_CONTROL0 0x086c +#define AVP_0_INT_STATUS 0x3810 +#define AVP_0_INT_CLEAR 0x3818 +#define AUDIO_INTERFACE_CONFIG0 0x0820 +#define AUD_IF_I2S 0x1 +#define AUD_IF_SEL_MASK 0x3 +#define AUD_FIFO_INIT_ON_OVF RT_BIT(2) +#define I2S_LINE0_EN RT_BIT(4) +#define I2S_LINES_EN_MASK (0xfU << 4) +#define I2S_BPCUV_RCV_MASK RT_BIT(12) +#define AUD_FORMAT_MASK (0x7U << 24) +#define AUD_FORMAT_ASP 0 +#define AUDIO_INTERFACE_CONTROL0 0x082c +#define AUDIO_FIFO_CLEAR RT_BIT(0) +#define PKTSCHED_PKT_EN 0x0aa8 +#define PKTSCHED_AVI_TX_EN RT_BIT(13) +#define PKTSCHED_GCP_TX_EN RT_BIT(3) +#define PKTSCHED_PKT_CONTROL0 0x0aac +#define PKTSCHED_CLEAR_AVMUTE 2U +#define PKT_AVI_CONTENTS0 0x0be0 +#define PKT_AVI_CONTENTS1 0x0be4 +#define PKT_AVI_CONTENTS2 0x0be8 +#define PKT_AVI_CONTENTS3 0x0bec +#define PKT_AVI_CONTENTS4 0x0bf0 +#define PKTSCHED_PKT_CONFIG1 0x0a9c +#define PKTSCHED_AVI_FIELDRATE RT_BIT(12) +#define FLT_CONFIG0 0x0160 +#define SCRAMB_CONFIG0 0x0960 +#define PKTSCHED_AUDI_TX_EN RT_BIT(15) +#define PKTSCHED_AMD_TX_EN RT_BIT(8) +#define PKTSCHED_AUDS_TX_EN RT_BIT(2) +#define PKTSCHED_ACR_TX_EN RT_BIT(1) +#define PKT_AUDI_CONTENTS0 0x0c20 +#define PKT_AUDI_CONTENTS1 0x0c24 +#define PKT_AUDI_CONTENTS2 0x0c28 +#define AUDPKT_CONTROL0 0x0e20 +#define AUDPKT_PBIT_FORCE_EN RT_BIT(12) +#define AUDPKT_CHSTATUS_OVR_EN RT_BIT(0) +#define AUDPKT_ACR_CONTROL0 0x0e40 +#define AUDPKT_ACR_N_MASK 0xfffffU +#define AUDPKT_ACR_CONTROL1 0x0e44 +#define AUDPKT_ACR_CTS_OVR_EN RT_BIT(1) +#define AUDPKT_CHSTATUS_OVR0 0x0e60 +#define AUDPKT_CHSTATUS_OVR1 0x0e64 + +struct rk3588_hdmi +{ + void *regs; + struct rt_syscon *grf; + struct rt_syscon *vo1_grf; + struct rt_phye *phy; + struct rt_device *dev; + struct rt_clk *clks[RK3588_HDMI_CONTROLLER_CLK_COUNT]; + struct rt_clk *link_clk; + struct rt_clk *dclk; + struct rt_reset_control *ref_rst; + struct rt_reset_control *hdp_rst; + struct display_timing mode; + rt_uint8_t id; + rt_bool_t clks_enabled; + rt_bool_t link_clk_enabled; + rt_bool_t dclk_enabled; + rt_bool_t prepared; + rt_bool_t enabled; + rt_uint32_t audio_rate; + rt_uint32_t audio_n; + rt_bool_t audio_configured; + rt_bool_t audio_enabled; +}; + +rt_inline rt_uint32_t rk3588_hdmi_read(struct rk3588_hdmi *hdmi, rt_uint32_t reg) +{ + return HWREG32((rt_uint8_t *)hdmi->regs + reg); +} + +rt_inline void rk3588_hdmi_write(struct rk3588_hdmi *hdmi, rt_uint32_t reg, + rt_uint32_t val) +{ + HWREG32((rt_uint8_t *)hdmi->regs + reg) = val; + rt_hw_dsb(); +} + +static void rk3588_hdmi_mod(struct rk3588_hdmi *hdmi, rt_uint32_t reg, + rt_uint32_t mask, rt_uint32_t val) +{ + rt_uint32_t tmp; + + tmp = rk3588_hdmi_read(hdmi, reg); + + tmp &= ~mask; + rk3588_hdmi_write(hdmi, reg, tmp | (val & mask)); +} + +static rt_uint32_t rk3588_hdmi_audio_n(rt_uint64_t tmds, rt_uint32_t rate) +{ + rt_uint32_t min_n; + rt_uint32_t max_n; + rt_uint32_t ideal_n; + rt_uint32_t best_n = 0; + rt_uint64_t best_rem = ~(rt_uint64_t)0; + rt_uint32_t n; + + if (!rate || !tmds) + { + return 0; + } + + min_n = (128U * rate + 1499U) / 1500U; + max_n = (128U * rate) / 300U; + ideal_n = (128U * rate) / 1000U; + + for (n = min_n; n <= max_n; ++n) + { + rt_uint64_t rem = (tmds * n) % (128ULL * rate); + + if (rem < best_rem || (rem == best_rem && + (n > ideal_n ? n - ideal_n : ideal_n - n) < + (best_n > ideal_n ? best_n - ideal_n : ideal_n - best_n))) + { + best_n = n; + best_rem = rem; + } + + if (!best_rem && n > ideal_n && n - ideal_n > + (best_n > ideal_n ? best_n - ideal_n : ideal_n - best_n)) + { + break; + } + } + + return best_n; +} + +static rt_uint32_t rk3588_hdmi_audio_cs_rate(rt_uint32_t rate) +{ + switch (rate) + { + case 32000: + return 0x3; + case 44100: + return 0x0; + case 48000: + return 0x2; + case 88200: + return 0x8; + case 96000: + return 0xa; + case 176400: + return 0xc; + case 192000: + return 0xe; + default: + return 0x1; + } +} + +static rt_err_t rk3588_hdmi_qp_audio_get(struct rt_ofw_node *np, + struct rk3588_hdmi **hdmi) +{ + if (!np || !hdmi || !rt_ofw_node_is_compatible(np, "rockchip,rk3588-dw-hdmi")) + { + return -RT_ENOSYS; + } + + *hdmi = rt_ofw_data(np); + return *hdmi ? RT_EOK : -RT_ENOSYS; +} + +rt_err_t rk3588_hdmi_qp_audio_enable(struct rt_ofw_node *np, rt_bool_t enable) +{ + struct rk3588_hdmi *hdmi; + rt_err_t err; + + if ((err = rk3588_hdmi_qp_audio_get(np, &hdmi))) + { + return err; + } + + if (enable && !hdmi->enabled) + { + return -RT_EBUSY; + } + + if (enable) + { + if (hdmi->audio_enabled) + { + return RT_EOK; + } + + rk3588_hdmi_mod(hdmi, GLOBAL_SWDISABLE, AVP_AUDIO_SWDISABLE, 0); + } + else + { + rk3588_hdmi_mod(hdmi, GLOBAL_SWDISABLE, AVP_AUDIO_SWDISABLE, + AVP_AUDIO_SWDISABLE); + } + + hdmi->audio_enabled = enable; + return RT_EOK; +} + +static void rk3588_hdmi_qp_audio_init(struct rk3588_hdmi *hdmi) +{ + rt_uint32_t n; + + n = rk3588_hdmi_audio_n(hdmi->mode.pixelclock, 48000); + + /* Match the QP core's display-enable audio startup sequence. */ + rk3588_hdmi_write(hdmi, AUDIO_INTERFACE_CONTROL0, AUDIO_FIFO_CLEAR); + rk3588_hdmi_mod(hdmi, AUDIO_INTERFACE_CONFIG0, AUD_IF_SEL_MASK, AUD_IF_I2S); + rk3588_hdmi_mod(hdmi, AUDIO_INTERFACE_CONFIG0, I2S_LINES_EN_MASK, I2S_LINE0_EN); + rk3588_hdmi_mod(hdmi, AUDIO_INTERFACE_CONFIG0, + I2S_BPCUV_RCV_MASK | AUD_FORMAT_MASK, AUD_FORMAT_ASP); + rk3588_hdmi_mod(hdmi, AUDIO_INTERFACE_CONFIG0, + AUD_FIFO_INIT_ON_OVF, AUD_FIFO_INIT_ON_OVF); + rk3588_hdmi_mod(hdmi, AUDPKT_ACR_CONTROL0, AUDPKT_ACR_N_MASK, n); + rk3588_hdmi_mod(hdmi, AUDPKT_ACR_CONTROL1, + AUDPKT_ACR_CTS_OVR_EN, 0); + rk3588_hdmi_write(hdmi, AUDPKT_CHSTATUS_OVR0, 0); + rk3588_hdmi_write(hdmi, AUDPKT_CHSTATUS_OVR1, + rk3588_hdmi_audio_cs_rate(48000) | (2U << 8)); + rk3588_hdmi_write(hdmi, AUDPKT_CONTROL0, + AUDPKT_PBIT_FORCE_EN | AUDPKT_CHSTATUS_OVR_EN); + rk3588_hdmi_write(hdmi, PKT_AUDI_CONTENTS1, 0x70U | (1U << 8)); + rk3588_hdmi_write(hdmi, PKT_AUDI_CONTENTS2, 0); + rk3588_hdmi_write(hdmi, PKTSCHED_PKT_EN, + PKTSCHED_AVI_TX_EN | PKTSCHED_GCP_TX_EN | + PKTSCHED_ACR_TX_EN | PKTSCHED_AUDS_TX_EN | PKTSCHED_AUDI_TX_EN | + PKTSCHED_AMD_TX_EN); + rk3588_hdmi_mod(hdmi, GLOBAL_SWDISABLE, AVP_AUDIO_SWDISABLE, 0); + + hdmi->audio_rate = 48000; + hdmi->audio_n = n; + hdmi->audio_enabled = RT_TRUE; + hdmi->audio_configured = RT_TRUE; +} + +rt_err_t rk3588_hdmi_qp_audio_configure(struct rt_ofw_node *np, + rt_uint32_t samplerate, rt_uint8_t channels, rt_uint8_t samplebits) +{ + struct rk3588_hdmi *hdmi; + rt_uint32_t n; + rt_uint32_t cs3; + rt_err_t err; + + if ((err = rk3588_hdmi_qp_audio_get(np, &hdmi))) + { + return err; + } + + if (!hdmi->enabled) + { + return -RT_EBUSY; + } + if (channels != 2 || + (samplebits != 16 && samplebits != 24 && samplebits != 32)) + { + return -RT_EINVAL; + } + + n = rk3588_hdmi_audio_n(hdmi->mode.pixelclock, samplerate); + if (!n) + { + return -RT_EINVAL; + } + + if (hdmi->audio_configured && hdmi->audio_rate == samplerate && + channels == 2 && samplebits == 16) + { + return RT_EOK; + } + + /* + * The QP audio register block is clocked by the AVP audio path. Linux + * enables that path from audio_startup before hw_params touches 0x820. + */ + /* + * The AVP audio datapath was initialized while enabling the link. A + * second software reset here makes the QP audio register window stop + * responding on RK3588 before the FIFO clear can complete. + */ + rk3588_hdmi_mod(hdmi, PKTSCHED_PKT_EN, + PKTSCHED_ACR_TX_EN | PKTSCHED_AUDS_TX_EN | + PKTSCHED_AUDI_TX_EN, 0); + + /* The FIFO was cleared during link enable while this QP window was live. */ + rk3588_hdmi_mod(hdmi, AUDIO_INTERFACE_CONFIG0, AUD_IF_SEL_MASK, AUD_IF_I2S); + rk3588_hdmi_mod(hdmi, AUDIO_INTERFACE_CONFIG0, I2S_LINES_EN_MASK, I2S_LINE0_EN); + rk3588_hdmi_mod(hdmi, AUDIO_INTERFACE_CONFIG0, + I2S_BPCUV_RCV_MASK | AUD_FORMAT_MASK, AUD_FORMAT_ASP); + rk3588_hdmi_mod(hdmi, AUDIO_INTERFACE_CONFIG0, + AUD_FIFO_INIT_ON_OVF, AUD_FIFO_INIT_ON_OVF); + + /* Use the hardware CTS generator; only N is programmed by the driver. */ + rk3588_hdmi_mod(hdmi, AUDPKT_ACR_CONTROL0, AUDPKT_ACR_N_MASK, n); + rk3588_hdmi_mod(hdmi, AUDPKT_ACR_CONTROL1, + AUDPKT_ACR_CTS_OVR_EN, 0); + + /* IEC60958 consumer PCM, stereo layout 0 and matching source rate. */ + rk3588_hdmi_write(hdmi, AUDPKT_CHSTATUS_OVR0, 0); + cs3 = rk3588_hdmi_audio_cs_rate(samplerate); + rk3588_hdmi_write(hdmi, AUDPKT_CHSTATUS_OVR1, cs3 | (2U << 8)); + rk3588_hdmi_write(hdmi, AUDPKT_CONTROL0, + AUDPKT_PBIT_FORCE_EN | AUDPKT_CHSTATUS_OVR_EN); + + /* HDMI Audio InfoFrame: LPCM, two channels, no channel allocation. */ + rk3588_hdmi_write(hdmi, PKT_AUDI_CONTENTS1, 0x70U | (1U << 8)); + rk3588_hdmi_write(hdmi, PKT_AUDI_CONTENTS2, 0); + rk3588_hdmi_write(hdmi, PKTSCHED_PKT_EN, + PKTSCHED_AVI_TX_EN | PKTSCHED_GCP_TX_EN | + PKTSCHED_ACR_TX_EN | PKTSCHED_AUDS_TX_EN | PKTSCHED_AUDI_TX_EN | + PKTSCHED_AMD_TX_EN); + + hdmi->audio_rate = samplerate; + hdmi->audio_n = n; + hdmi->audio_configured = RT_TRUE; + LOG_D("HDMI%u audio %u Hz, %u ch, %u bit, N=%u", hdmi->id, + samplerate, channels, samplebits, n); + + return RT_EOK; +} + +static rt_err_t rk3588_hdmi_clks_enable(struct rk3588_hdmi *hdmi) +{ + rt_err_t err; + rt_size_t i; + + if (hdmi->clks_enabled) + { + return RT_EOK; + } + + for (i = 0; i < RK3588_HDMI_CONTROLLER_CLK_COUNT; ++i) + { + err = rt_clk_prepare_enable(hdmi->clks[i]); + if (err) + { + while (i) + { + rt_clk_disable_unprepare(hdmi->clks[--i]); + } + return err; + } + } + + hdmi->clks_enabled = RT_TRUE; + return RT_EOK; +} + +static void rk3588_hdmi_clks_disable(struct rk3588_hdmi *hdmi) +{ + rt_size_t i; + + if (!hdmi->clks_enabled) + { + return; + } + + for (i = RK3588_HDMI_CONTROLLER_CLK_COUNT; i; --i) + { + rt_clk_disable_unprepare(hdmi->clks[i - 1]); + } + + hdmi->clks_enabled = RT_FALSE; +} + +static rt_err_t rk3588_hdmi_reset_controller(struct rk3588_hdmi *hdmi) +{ + rt_err_t err; + + err = rt_reset_control_assert(hdmi->ref_rst); + if (err) + { + return err; + } + err = rt_reset_control_assert(hdmi->hdp_rst); + if (err) + { + return err; + } + + rt_hw_us_delay(10); + + err = rt_reset_control_deassert(hdmi->ref_rst); + if (err) + { + return err; + } + err = rt_reset_control_deassert(hdmi->hdp_rst); + if (err) + { + return err; + } + + return RT_EOK; +} + +static rt_err_t rk3588_hdmi_link_clk_enable(struct rk3588_hdmi *hdmi) +{ + rt_err_t err; + + if (hdmi->link_clk_enabled) + { + return RT_EOK; + } + + err = rt_clk_set_rate(hdmi->link_clk, hdmi->mode.pixelclock); + if (err) + { + return err; + } + + err = rt_clk_prepare_enable(hdmi->link_clk); + if (!err) + { + hdmi->link_clk_enabled = RT_TRUE; + } + + return err; +} + +static void rk3588_hdmi_link_clk_disable(struct rk3588_hdmi *hdmi) +{ + if (!hdmi->link_clk_enabled) + { + return; + } + + rt_clk_disable_unprepare(hdmi->link_clk); + hdmi->link_clk_enabled = RT_FALSE; +} + +static rt_err_t rk3588_hdmi_dclk_enable(struct rk3588_hdmi *hdmi) +{ + rt_err_t err; + + if (hdmi->dclk_enabled) + { + return RT_EOK; + } + + err = rt_clk_prepare_enable(hdmi->dclk); + if (!err) + { + hdmi->dclk_enabled = RT_TRUE; + } + + return err; +} + +static void rk3588_hdmi_dclk_disable(struct rk3588_hdmi *hdmi) +{ + if (!hdmi->dclk_enabled) + { + return; + } + + rt_clk_disable_unprepare(hdmi->dclk); + hdmi->dclk_enabled = RT_FALSE; +} + +static void rk3588_hdmi_io_path_init(struct rk3588_hdmi *hdmi) +{ + rt_uint32_t io_cfg; + rt_uint32_t hpd_mask; + rt_uint32_t grant; + rt_uint32_t vo1_con; + + io_cfg = RK3588_SCLIN | RK3588_SDAIN | RK3588_MODE | RK3588_I2S_SEL; + hpd_mask = hdmi->id ? RK3588_HDMI1_HPD_INT_MSK : + RK3588_HDMI0_HPD_INT_MSK; + grant = hdmi->id ? RK3588_HDMI1_GRANT_SEL : RK3588_HDMI0_GRANT_SEL; + vo1_con = hdmi->id ? RK3588_GRF_VO1_CON6 : RK3588_GRF_VO1_CON3; + + /* VOP2 presents the RT framebuffer to the QP as 8-bit RGB. */ + rt_syscon_write(hdmi->vo1_grf, hdmi->id ? RK3588_GRF_VO1_CON7 : RK3588_GRF_VO1_CON4, + RK3588_HDMI21_MASK << 16); + rt_syscon_write(hdmi->vo1_grf, vo1_con, + RK3588_RGB | + ((RK3588_COMPRESS_MODE_MASK | RK3588_COLOR_FORMAT_MASK | + RK3588_COLOR_DEPTH_MASK) << 16)); + + /* Match dw_hdmi_qp_rk3588_io_init(): do not disturb other VO1 fields. */ + rt_syscon_write(hdmi->vo1_grf, vo1_con, io_cfg | (io_cfg << 16)); + rt_syscon_write(hdmi->grf, RK3588_GRF_SOC_CON7, + RK3588_SET_HPD_PATH_MASK | (RK3588_SET_HPD_PATH_MASK << 16)); + rt_syscon_write(hdmi->vo1_grf, RK3588_GRF_VO1_CON9, grant | (grant << 16)); + rt_syscon_write(hdmi->grf, RK3588_GRF_SOC_CON2, hpd_mask | (hpd_mask << 16)); +} + +static void rk3588_hdmi_qp_configure_avi(struct rk3588_hdmi *hdmi) +{ + /* AVI Y=0 declares the RGB transport selected in VO1 GRF. */ + rt_uint8_t pb1; + rt_uint8_t pb2; + rt_uint8_t pb3; + rt_uint8_t pb4 = 0; /* no CTA VIC for 2560x1440p60 */ + rt_uint8_t pb5 = 0; /* default RGB quantization range */ + rt_uint8_t checksum; + rt_uint32_t sum; + + pb1 = RK3588_RGB << 5; + pb2 = 0; + pb3 = 0; + + if (hdmi->mode.hactive == 1280 && hdmi->mode.vactive == 720 && + hdmi->mode.pixelclock == 74250000) + { + pb4 = 4; /* CEA-861 720p60 */ + } + + sum = 0x82 + 2 + 13 + pb1 + pb2 + pb3 + pb4 + pb5; + checksum = (rt_uint8_t)(0U - sum); + + /* HDMI AVI v2: header lives in CONTENTS0, then checksum and PB1..PB13. */ + rk3588_hdmi_write(hdmi, PKT_AVI_CONTENTS0, (2U << 8) | (13U << 16)); + rk3588_hdmi_write(hdmi, PKT_AVI_CONTENTS1, + checksum | ((rt_uint32_t)pb1 << 8) | + ((rt_uint32_t)pb2 << 16) | ((rt_uint32_t)pb3 << 24)); + rk3588_hdmi_write(hdmi, PKT_AVI_CONTENTS2, + pb4 | ((rt_uint32_t)pb5 << 8)); + rk3588_hdmi_write(hdmi, PKT_AVI_CONTENTS3, 0); + rk3588_hdmi_write(hdmi, PKT_AVI_CONTENTS4, 0); + rk3588_hdmi_write(hdmi, PKTSCHED_PKT_CONFIG1, 0); +} + +static void rk3588_hdmi_qp_init(struct rk3588_hdmi *hdmi) +{ + /* The controller reset leaves the AVP datapath software-disabled. */ + rk3588_hdmi_write(hdmi, GLOBAL_SWRESET_REQUEST, AVP_VIDEO_SWINIT); + rt_hw_us_delay(1); + rk3588_hdmi_mod(hdmi, GLOBAL_SWDISABLE, AVP_VIDEO_SWDISABLE, 0); + + rk3588_hdmi_write(hdmi, MAINUNIT_0_INT_MASK_N, 0); + rk3588_hdmi_write(hdmi, MAINUNIT_1_INT_MASK_N, 0); + rk3588_hdmi_write(hdmi, AVP_1_INT_MASK_N, 0); + rk3588_hdmi_write(hdmi, AVP_3_INT_MASK_N, 0); + rk3588_hdmi_write(hdmi, TIMER_BASE_CONFIG0, 428571429U); + + /* Match dw_hdmi_qp_init_hw() after every controller reset. */ + rk3588_hdmi_write(hdmi, I2CM_CONTROL0, 1); + rk3588_hdmi_write(hdmi, I2CM_FM_SCL_CONFIG0, 0x085c085cU); + rk3588_hdmi_write(hdmi, I2CM_INTERFACE_CONTROL0, 0); + rk3588_hdmi_write(hdmi, MAINUNIT_1_INT_CLEAR, + I2CM_OP_DONE_CLEAR | I2CM_NACK_RCVD_CLEAR); + rk3588_hdmi_write(hdmi, AVP_0_INT_CLEAR, 0xffffffffU); + + rk3588_hdmi_write(hdmi, LINK_CONFIG0, 0); + rk3588_hdmi_write(hdmi, HDCP2LOGIC_CONFIG0, HDCP2_BYPASS); + + rk3588_hdmi_write(hdmi, FRAME_COMPOSER_CONFIG9, 2U << 8); + rk3588_hdmi_write(hdmi, FLT_CONFIG0, 0); + rk3588_hdmi_write(hdmi, SCRAMB_CONFIG0, 0); + rk3588_hdmi_qp_configure_avi(hdmi); + rk3588_hdmi_write(hdmi, PKTSCHED_PKT_EN, + PKTSCHED_AVI_TX_EN | PKTSCHED_GCP_TX_EN); + + /* Let the QP accept the RGB stream presented by VOP2. */ + rk3588_hdmi_write(hdmi, VIDEO_INTERFACE_CONFIG0, + VIDEO_INTERFACE_ENABLE); + + LOG_D("HDMI%u QP TMDS video path configured", hdmi->id); +} + +static void rk3588_hdmi_qp_log_state(struct rk3588_hdmi *hdmi) +{ + LOG_D("HDMI%u QP state: cfg0=%08x swdis=%08x cmu=%08x main=%08x " + "vif=%08x fc=%08x pkt=%08x avp0=%08x", + hdmi->id, + rk3588_hdmi_read(hdmi, VIDEO_INTERFACE_CONFIG0), + rk3588_hdmi_read(hdmi, GLOBAL_SWDISABLE), + rk3588_hdmi_read(hdmi, CMU_STATUS), + rk3588_hdmi_read(hdmi, MAINUNIT_STATUS0), + rk3588_hdmi_read(hdmi, VIDEO_INTERFACE_STATUS0), + rk3588_hdmi_read(hdmi, FRAME_COMPOSER_CONTROL0), + rk3588_hdmi_read(hdmi, PKTSCHED_PKT_EN), + rk3588_hdmi_read(hdmi, AVP_0_INT_STATUS)); +} + +static rt_err_t rk3588_hdmi_prepare(void *priv, rt_uint32_t vp_id, + const struct display_timing *tim) +{ + struct rk3588_hdmi *hdmi = priv; + rt_err_t err; + + if (!hdmi || !tim) + { + return -RT_EINVAL; + } + + /* The display framework calls connector prepare before mode_set. */ + hdmi->mode = *tim; + if (!hdmi->mode.htotal || !hdmi->mode.vtotal) + { + display_timing_derive(&hdmi->mode); + } + if (!hdmi->mode.pixelclock || + hdmi->mode.pixelclock > RK3588_HDMI_TMDS_MAX_RATE) + { + return -RT_EINVAL; + } + + if (hdmi->prepared) + { + return RT_EOK; + } + + { + char name[16]; + + rt_snprintf(name, sizeof(name), "dclk_vp%u", vp_id); + hdmi->dclk = rt_clk_get_by_name(hdmi->dev, name); + if (rt_is_err_or_null(hdmi->dclk)) + { + LOG_E("HDMI%u %s unavailable", hdmi->id, name); + return -RT_ENOENT; + } + } + + /* The first probe can defer on the HDMI PHY before PM domains exist. */ + if (!hdmi->dev->power_domain_unit) + { + err = rt_dm_power_domain_attach(hdmi->dev, RT_TRUE); + if (err) + { + LOG_E("HDMI%u VO1 power domain unavailable: %s", hdmi->id, + rt_strerror(err)); + rt_clk_put(hdmi->dclk); + hdmi->dclk = RT_NULL; + return err; + } + } + + err = rk3588_hdmi_clks_enable(hdmi); + if (err) + { + rt_clk_put(hdmi->dclk); + hdmi->dclk = RT_NULL; + return err; + } + + err = rk3588_hdmi_reset_controller(hdmi); + if (err) + { + rk3588_hdmi_clks_disable(hdmi); + rt_clk_put(hdmi->dclk); + hdmi->dclk = RT_NULL; + return err; + } + + err = rk3588_hdmi_link_clk_enable(hdmi); + if (err) + { + LOG_E("HDMI%u link clock failed: %d", hdmi->id, err); + rk3588_hdmi_clks_disable(hdmi); + rt_clk_put(hdmi->dclk); + hdmi->dclk = RT_NULL; + return err; + } + + rk3588_hdmi_io_path_init(hdmi); + hdmi->prepared = RT_TRUE; + + return RT_EOK; +} + +static rt_err_t rk3588_hdmi_mode_set(void *priv, const struct display_timing *tim) +{ + struct rk3588_hdmi *hdmi = priv; + + if (!hdmi || !tim) + { + return -RT_EINVAL; + } + + hdmi->mode = *tim; + if (!hdmi->mode.htotal || !hdmi->mode.vtotal) + { + display_timing_derive(&hdmi->mode); + } + + if (!hdmi->mode.pixelclock || + hdmi->mode.pixelclock > RK3588_HDMI_TMDS_MAX_RATE) + { + LOG_E("unsupported TMDS rate %u Hz", (rt_uint32_t)hdmi->mode.pixelclock); + return -RT_EINVAL; + } + + return RT_EOK; +} + +static rt_err_t rk3588_hdmi_detect(void *priv, rt_bool_t *connected) +{ + struct rk3588_hdmi *hdmi = priv; + rt_uint32_t status; + rt_err_t err; + + if (!hdmi || !connected) + { + return -RT_EINVAL; + } + + err = rt_syscon_read(hdmi->grf, RK3588_GRF_SOC_STATUS1, &status); + if (err) + { + return err; + } + + *connected = !!(status & (hdmi->id ? RK3588_HDMI1_LEVEL_INT : + RK3588_HDMI0_LEVEL_INT)); + + return RT_EOK; +} + +static rt_err_t rk3588_hdmi_enable(void *priv, rt_bool_t enable) +{ + struct rk3588_hdmi *hdmi = priv; + union rt_phye_configure_opts opts = { 0 }; + rt_err_t err; + rt_bool_t phy_inited = RT_FALSE; + + if (!hdmi) + { + return -RT_EINVAL; + } + if (!enable) + { + if (hdmi->enabled) + { + rt_phye_power_off(hdmi->phy); + rt_phye_exit(hdmi->phy); + hdmi->enabled = RT_FALSE; + } + rk3588_hdmi_dclk_disable(hdmi); + if (!rt_is_err_or_null(hdmi->dclk)) + { + rt_clk_put(hdmi->dclk); + hdmi->dclk = RT_NULL; + } + rk3588_hdmi_link_clk_disable(hdmi); + return RT_EOK; + } + if (hdmi->enabled) + { + return RT_EOK; + } + + if (!hdmi->prepared) + { + err = rk3588_hdmi_prepare(hdmi, 0, &hdmi->mode); + if (err) + { + return err; + } + } + + /* Mirror dw_hdmi_dclk_set(): the QP owns one dclk reference as well. */ + err = rk3588_hdmi_dclk_enable(hdmi); + if (err) + { + LOG_E("HDMI%u dclk enable failed: %d", hdmi->id, err); + return err; + } + + /* VOP has enabled dclk_vp* before connector enable. */ + opts.hdmi.pixel_clock = hdmi->mode.pixelclock; + opts.hdmi.tmds_clock = hdmi->mode.pixelclock; + err = rt_phye_set_mode_simple(hdmi->phy, RT_PHYE_MODE_HDMI); + if (!err) + { + err = rt_phye_configure(hdmi->phy, &opts); + } + if (!err) + { + err = rt_phye_init(hdmi->phy); + phy_inited = !err; + } + if (!err) + { + err = rt_phye_power_on(hdmi->phy); + } + if (err) + { + if (phy_inited) + { + rt_phye_exit(hdmi->phy); + } + rk3588_hdmi_dclk_disable(hdmi); + LOG_E("HDPTX setup failed: %d", err); + return err; + } + + /* + * The QP register file is clocked by the HDPTX PLL. EDK2 configures + * that PLL before accessing the QP, then switches it into TMDS mode. + * Keep the same ordering so a cold boot never programs an unclocked QP. + */ + rk3588_hdmi_qp_init(hdmi); + rk3588_hdmi_qp_audio_init(hdmi); + /* + * The sink sees AVMUTE until the PHY has had time to train. U-Boot + * waits 50 ms here; 50 us is not sufficient on a cold boot. + */ + rt_hw_us_delay(50000); + rk3588_hdmi_write(hdmi, PKTSCHED_PKT_CONTROL0, PKTSCHED_CLEAR_AVMUTE); + rk3588_hdmi_mod(hdmi, PKTSCHED_PKT_EN, + PKTSCHED_AVI_TX_EN | PKTSCHED_GCP_TX_EN, + PKTSCHED_AVI_TX_EN | PKTSCHED_GCP_TX_EN); + rk3588_hdmi_qp_log_state(hdmi); + + hdmi->enabled = RT_TRUE; + LOG_D("HDMI%u TMDS enabled: %ux%u@%uHz", hdmi->id, + hdmi->mode.hactive, hdmi->mode.vactive, + (rt_uint32_t)hdmi->mode.pixelclock); + + return RT_EOK; +} + +static rt_err_t rk3588_hdmi_disable(void *priv) +{ + struct rk3588_hdmi *hdmi = priv; + + if (!hdmi) + { + return -RT_EINVAL; + } + + rk3588_hdmi_enable(hdmi, RT_FALSE); + rk3588_hdmi_clks_disable(hdmi); + hdmi->prepared = RT_FALSE; + + return RT_EOK; +} + +static const struct rockchip_connector_ops rk3588_hdmi_ops = +{ + .detect = rk3588_hdmi_detect, + .prepare = rk3588_hdmi_prepare, + .mode_set = rk3588_hdmi_mode_set, + .enable = rk3588_hdmi_enable, + .disable = rk3588_hdmi_disable, +}; + +static rt_err_t rk3588_hdmi_probe(struct rt_platform_device *pdev) +{ + static const char * const clock_names[] = + { + "pclk", "hpd", "earc", "hdmitx_ref", "aud", "hclk_vo1", + }; + struct rt_device *dev = &pdev->parent; + struct rk3588_hdmi *hdmi; + rt_size_t i; + rt_err_t err; + + hdmi = rt_calloc(1, sizeof(*hdmi)); + if (!hdmi) + { + return -RT_ENOMEM; + } + + hdmi->dev = dev; + hdmi->regs = rt_ofw_iomap(dev->ofw_node, 0); + hdmi->grf = rt_syscon_find_by_ofw_phandle(dev->ofw_node, "rockchip,grf"); + hdmi->vo1_grf = rt_syscon_find_by_ofw_phandle(dev->ofw_node, + "rockchip,vo1_grf"); + if (!hdmi->regs || !hdmi->grf || !hdmi->vo1_grf) + { + LOG_E("%s: MMIO/GRF resource unavailable", + rt_ofw_node_full_name(dev->ofw_node)); + err = -RT_EIO; + goto _fail; + } + hdmi->id = rt_ofw_get_alias_id(dev->ofw_node, "hdmi"); + if (hdmi->id < 0 || hdmi->id > 1) + { + LOG_E("%s: invalid HDMI alias ID %d", rt_ofw_node_full_name(dev->ofw_node), + hdmi->id); + err = -RT_EINVAL; + goto _fail; + } + + for (i = 0; i < RT_ARRAY_SIZE(hdmi->clks); ++i) + { + hdmi->clks[i] = rt_clk_get_by_name(dev, clock_names[i]); + if (rt_is_err_or_null(hdmi->clks[i])) + { + LOG_E("%s: clock '%s' unavailable", rt_ofw_node_full_name(dev->ofw_node), + clock_names[i]); + err = -RT_EIO; + goto _fail; + } + } + + hdmi->ref_rst = rt_reset_control_get_by_name(dev, "ref"); + hdmi->hdp_rst = rt_reset_control_get_by_name(dev, "hdp"); + if (rt_is_err_or_null(hdmi->ref_rst) || rt_is_err_or_null(hdmi->hdp_rst)) + { + LOG_E("%s: HDMI reset controls unavailable", + rt_ofw_node_full_name(dev->ofw_node)); + err = -RT_EIO; + goto _fail; + } + + hdmi->link_clk = rt_clk_get_by_name(dev, "link_clk"); + if (rt_is_err_or_null(hdmi->link_clk)) + { + LOG_E("%s: link clock unavailable", + rt_ofw_node_full_name(dev->ofw_node)); + err = -RT_EIO; + goto _fail; + } + + hdmi->phy = rt_phye_get_by_name(dev, "hdmi"); + if (rt_is_err_or_null(hdmi->phy)) + { + LOG_E("%s: HDMI PHY unavailable", + rt_ofw_node_full_name(dev->ofw_node)); + err = -RT_EIO; + goto _fail; + } + + err = rockchip_display_register_connector(dev->ofw_node, &rk3588_hdmi_ops, hdmi); + if (err) + { + LOG_E("%s: connector registration failed: %d", + rt_ofw_node_full_name(dev->ofw_node), err); + goto _fail; + } + + dev->user_data = hdmi; + rt_dm_dev_bind_fwdata(dev, RT_NULL, hdmi); + + return RT_EOK; + +_fail: + if (!rt_is_err_or_null(hdmi->phy)) + { + rt_phye_put(hdmi->phy); + } + if (!rt_is_err_or_null(hdmi->hdp_rst)) + { + rt_reset_control_put(hdmi->hdp_rst); + } + if (!rt_is_err_or_null(hdmi->ref_rst)) + { + rt_reset_control_put(hdmi->ref_rst); + } + if (!rt_is_err_or_null(hdmi->link_clk)) + { + rt_clk_put(hdmi->link_clk); + } + for (i = 0; i < RT_ARRAY_SIZE(hdmi->clks); ++i) + { + if (!rt_is_err_or_null(hdmi->clks[i])) + { + rt_clk_put(hdmi->clks[i]); + } + } + if (hdmi->regs) + { + rt_iounmap(hdmi->regs); + } + rt_free(hdmi); + + return err; +} + +static const struct rt_ofw_node_id rk3588_hdmi_ids[] = +{ + { .compatible = "rockchip,rk3588-dw-hdmi" }, + { /* sentinel */ } +}; + +static struct rt_platform_driver rk3588_hdmi_driver = +{ + .name = "dw-hdmi-qp-rk3588", + .ids = rk3588_hdmi_ids, + .probe = rk3588_hdmi_probe, +}; +RT_PLATFORM_DRIVER_EXPORT(rk3588_hdmi_driver); diff --git a/bsp/rockchip/dm/graphic/framebuffer/hdmi-rockchip.c b/bsp/rockchip/dm/graphic/framebuffer/hdmi-rockchip.c index 020d95eb5ad4..d4a54957f774 100644 --- a/bsp/rockchip/dm/graphic/framebuffer/hdmi-rockchip.c +++ b/bsp/rockchip/dm/graphic/framebuffer/hdmi-rockchip.c @@ -20,6 +20,9 @@ #define RK3568_HDMI_SDAIN_MSK ((1U << 15) | (1U << (15 + 16))) #define RK3568_HDMI_SCLIN_MSK ((1U << 14) | (1U << (14 + 16))) #define RK_HDMI_PHY_I2C_TIMEOUT_US 20000 +#define RK3528_VO_GRF_HDMI_MASK 0x60014 +#define RK3528_HDMI_SNKDET_SEL RT_BIT(6) +#define RK3528_HDMI_SNKDET RT_BIT(5) struct rk_hdmi_phy_config { @@ -57,6 +60,14 @@ struct rk_hdmi rt_bool_t audio_configured; rt_bool_t audio_enabled; rt_uint32_t audio_n; +#ifdef RT_PHYE_ROCKCHIP_INNO_HDMI + struct rt_phye *phy; + void *hpd_regs; + rt_ssize_t hpd_pin; + rt_uint8_t hpd_active; + rt_bool_t phy_initialized; + rt_bool_t phy_powered; +#endif }; #if DBG_LVL >= DBG_LOG @@ -193,6 +204,12 @@ static void rk_hdmi_audio_set_n(struct rk_hdmi *hdmi, rt_uint32_t n) rt_err_t rk_hdmi_audio_enable(struct rt_ofw_node *np, rt_bool_t enable) { rt_uint8_t clkdis; + + if (rt_ofw_node_is_compatible(np, "rockchip,rk3588-dw-hdmi")) + { + return rk3588_hdmi_qp_audio_enable(np, enable); + } + struct rk_hdmi *hdmi = rk_hdmi_from_np(np); if (!hdmi) @@ -229,6 +246,12 @@ rt_err_t rk_hdmi_audio_configure(struct rt_ofw_node *np, rt_uint32_t samplerate, rt_uint8_t channels, rt_uint8_t samplebits) { rt_uint8_t conf0, conf1; + + if (rt_ofw_node_is_compatible(np, "rockchip,rk3588-dw-hdmi")) + { + return rk3588_hdmi_qp_audio_configure(np, samplerate, channels, samplebits); + } + struct rk_hdmi *hdmi = rk_hdmi_from_np(np); rt_uint32_t n = rk_hdmi_audio_n(samplerate); @@ -793,6 +816,33 @@ static void rk_hdmi_clks_disable(struct rk_hdmi *hdmi) hdmi->clks_enabled = RT_FALSE; } +#ifdef RT_PHYE_ROCKCHIP_INNO_HDMI +static rt_err_t rk3528_hdmi_hpd(struct rk_hdmi *hdmi, rt_bool_t *connected) +{ + rt_uint32_t mask = RT_BIT(0) | RT_BIT(1) | RK3528_HDMI_SNKDET_SEL; + rt_uint32_t val = RT_BIT(0) | RT_BIT(1); + + if (hdmi->hpd_pin >= 0) + { + /* RK3528 GPIO0_A2 input enable is gated by its output data bit. */ + HWREG32(hdmi->hpd_regs) = (RT_BIT(2) << 16) | RT_BIT(2); + *connected = rt_pin_read(hdmi->hpd_pin) == hdmi->hpd_active; + mask |= RK3528_HDMI_SNKDET; + val |= RK3528_HDMI_SNKDET_SEL; + if (*connected) + { + val |= RK3528_HDMI_SNKDET; + } + } + else + { + *connected = !!(rk_hdmi_read(hdmi, HDMI_PHY_STAT0) & HDMI_PHY_HPD); + } + return rt_syscon_write(hdmi->grf, RK3528_VO_GRF_HDMI_MASK, + (mask << 16) | val); +} +#endif /* RT_PHYE_ROCKCHIP_INNO_HDMI */ + static rt_err_t rk_hdmi_detect(void *priv, rt_bool_t *connected) { struct rk_hdmi *hdmi = priv; @@ -819,6 +869,17 @@ static rt_err_t rk_hdmi_detect(void *priv, rt_bool_t *connected) } *connected = RT_FALSE; +#ifdef RT_PHYE_ROCKCHIP_INNO_HDMI + if (hdmi->phy) + { + err = rk3528_hdmi_hpd(hdmi, connected); + if (enabled_for_detect && (err || !*connected)) + { + rk_hdmi_clks_disable(hdmi); + } + return err; + } +#endif /* RT_PHYE_ROCKCHIP_INNO_HDMI */ for (sample = 0; sample < 3; ++sample) { if (rk_hdmi_read(hdmi, HDMI_PHY_STAT0) & HDMI_PHY_HPD) @@ -868,6 +929,35 @@ static rt_err_t rk_hdmi_prepare(void *priv, rt_uint32_t vp_id, const struct disp return err; } +#ifdef RT_PHYE_ROCKCHIP_INNO_HDMI + if (hdmi->phy) + { + rt_bool_t connected; + union rt_phye_configure_opts opts = { 0 }; + + opts.hdmi.pixel_clock = tim->pixelclock; + opts.hdmi.tmds_clock = tim->pixelclock; + err = rk3528_hdmi_hpd(hdmi, &connected); + if (!err) + err = rt_phye_init(hdmi->phy); + if (!err) + { + hdmi->phy_initialized = RT_TRUE; + err = rt_phye_set_mode(hdmi->phy, RT_PHYE_MODE_HDMI, 0); + } + if (!err) + err = rt_phye_configure(hdmi->phy, &opts); + if (err) + { + if (hdmi->phy_initialized) + rt_phye_exit(hdmi->phy); + hdmi->phy_initialized = RT_FALSE; + rk_hdmi_clks_disable(hdmi); + return err; + } + } + else +#endif /* RT_PHYE_ROCKCHIP_INNO_HDMI */ if (hdmi->grf) { rt_syscon_write(hdmi->grf, RK3568_GRF_VO_CON1, @@ -931,7 +1021,18 @@ static rt_err_t rk_hdmi_enable(void *priv, rt_bool_t enable) } rk_hdmi_write(hdmi, HDMI_MC_CLKDIS, 0x7f); - rk_hdmi_phy_power_off(hdmi); +#ifdef RT_PHYE_ROCKCHIP_INNO_HDMI + if (hdmi->phy) + { + if (hdmi->phy_powered) + rt_phye_power_off(hdmi->phy); + hdmi->phy_powered = RT_FALSE; + } + else +#endif /* RT_PHYE_ROCKCHIP_INNO_HDMI */ + { + rk_hdmi_phy_power_off(hdmi); + } hdmi->enabled = RT_FALSE; return RT_EOK; } @@ -974,7 +1075,17 @@ static rt_err_t rk_hdmi_enable(void *priv, rt_bool_t enable) rk_hdmi_video_csc(hdmi); rk_hdmi_video_sample(hdmi); - err = rk_hdmi_phy_init(hdmi); +#ifdef RT_PHYE_ROCKCHIP_INNO_HDMI + if (hdmi->phy) + { + err = rt_phye_power_on(hdmi->phy); + hdmi->phy_powered = !err; + } + else +#endif /* RT_PHYE_ROCKCHIP_INNO_HDMI */ + { + err = rk_hdmi_phy_init(hdmi); + } if (err) { rk_hdmi_write(hdmi, HDMI_MC_CLKDIS, 0x7f); @@ -1007,6 +1118,13 @@ static rt_err_t rk_hdmi_disable(void *priv) } rk_hdmi_enable(priv, RT_FALSE); +#ifdef RT_PHYE_ROCKCHIP_INNO_HDMI + if (hdmi->phy_initialized) + { + rt_phye_exit(hdmi->phy); + hdmi->phy_initialized = RT_FALSE; + } +#endif /* RT_PHYE_ROCKCHIP_INNO_HDMI */ rk_hdmi_clks_disable(hdmi); hdmi->prepared = RT_FALSE; @@ -1034,33 +1152,71 @@ static rt_err_t rk_hdmi_probe(struct rt_platform_device *pdev) return -RT_ENOMEM; } + if (rt_ofw_node_is_compatible(np, "rockchip,rk3528-dw-hdmi")) + { +#ifdef RT_PHYE_ROCKCHIP_INNO_HDMI + hdmi->phy = rt_phye_get_by_name(dev, "hdmi"); + hdmi->hpd_pin = -1; + if (rt_is_err_or_null(hdmi->phy)) + { + err = -RT_EEMPTY; + hdmi->phy = RT_NULL; + goto fail; + } + if (rt_ofw_get_prop(np, "hpd-gpios", RT_NULL)) + { + hdmi->hpd_pin = rt_pin_get_named_pin(dev, "hpd", 0, + RT_NULL, &hdmi->hpd_active); + if (hdmi->hpd_pin < 0) + { + err = hdmi->hpd_pin; + goto fail; + } + hdmi->hpd_regs = rt_ofw_iomap(np, 1); + if (!hdmi->hpd_regs) + { + err = -RT_EIO; + goto fail; + } + rt_pin_mode(hdmi->hpd_pin, PIN_MODE_INPUT); + } +#else + LOG_E("RK3528 requires RT_PHYE_ROCKCHIP_INNO_HDMI"); + err = -RT_ENOSYS; + goto fail; +#endif /* RT_PHYE_ROCKCHIP_INNO_HDMI */ + } + hdmi->regs = rt_ofw_iomap(np, 0); if (!hdmi->regs) { - rt_free(hdmi); - return -RT_EIO; + err = -RT_EIO; + goto fail; } hdmi->clks = rt_clk_get_array(dev); if (rt_is_err(hdmi->clks)) { - rt_iounmap(hdmi->regs); - rt_free(hdmi); - return rt_ptr_err(hdmi->clks); + err = rt_ptr_err(hdmi->clks); + goto fail; } hdmi->reg_io_width = 4; rt_dm_dev_prop_read_u32(dev, "reg-io-width", &hdmi->reg_io_width); hdmi->sink_is_hdmi = RT_TRUE; hdmi->grf = rt_syscon_find_by_ofw_phandle(np, "rockchip,grf"); +#ifdef RT_PHYE_ROCKCHIP_INNO_HDMI + if (hdmi->phy && rt_is_err_or_null(hdmi->grf)) + { + err = -RT_EIO; + goto fail; + } +#endif /* RT_PHYE_ROCKCHIP_INNO_HDMI */ err = rockchip_display_register_connector(np, &rk_hdmi_connector_ops, hdmi); if (err) { - rt_clk_array_put(hdmi->clks); - rt_iounmap(hdmi->regs); - rt_free(hdmi); - return err; + goto fail; } pdev->parent.user_data = hdmi; @@ -1069,6 +1225,33 @@ static rt_err_t rk_hdmi_probe(struct rt_platform_device *pdev) LOG_D("HDMI %s: reg-io-width=%u", rt_ofw_node_full_name(np), hdmi->reg_io_width); return RT_EOK; + +fail: + if (!rt_is_err_or_null(hdmi->clks)) + { + rt_clk_array_put(hdmi->clks); + } + + if (hdmi->regs) + { + rt_iounmap(hdmi->regs); + } + +#ifdef RT_PHYE_ROCKCHIP_INNO_HDMI + if (hdmi->hpd_regs) + { + rt_iounmap(hdmi->hpd_regs); + } + + if (hdmi->phy) + { + rt_phye_put(hdmi->phy); + } +#endif /* RT_PHYE_ROCKCHIP_INNO_HDMI */ + + rt_free(hdmi); + + return err; } static const struct rt_ofw_node_id rk_hdmi_ids[] = diff --git a/bsp/rockchip/dm/include/rockchip-hdmi.h b/bsp/rockchip/dm/include/rockchip-hdmi.h index f15e5c507dcb..d7d2d809647a 100644 --- a/bsp/rockchip/dm/include/rockchip-hdmi.h +++ b/bsp/rockchip/dm/include/rockchip-hdmi.h @@ -17,6 +17,9 @@ rt_err_t rk_hdmi_audio_configure(struct rt_ofw_node *np, rt_uint32_t samplerate, rt_uint8_t channels, rt_uint8_t samplebits); rt_err_t rk_hdmi_audio_enable(struct rt_ofw_node *np, rt_bool_t enable); +rt_err_t rk3588_hdmi_qp_audio_configure(struct rt_ofw_node *np, + rt_uint32_t samplerate, rt_uint8_t channels, rt_uint8_t samplebits); +rt_err_t rk3588_hdmi_qp_audio_enable(struct rt_ofw_node *np, rt_bool_t enable); #else rt_inline rt_err_t rk_hdmi_audio_configure(struct rt_ofw_node *np, rt_uint32_t samplerate, rt_uint8_t channels, rt_uint8_t samplebits) diff --git a/bsp/rockchip/dm/mfd/rk8xx.c b/bsp/rockchip/dm/mfd/rk8xx.c index 40655776a3c2..102e3b9b57bc 100755 --- a/bsp/rockchip/dm/mfd/rk8xx.c +++ b/bsp/rockchip/dm/mfd/rk8xx.c @@ -623,6 +623,14 @@ static const struct rk8xx_endpoint rk818_eps[] = }, }; +static rt_err_t rk806_power_off_prepare(struct rt_device *dev) +{ + struct rk8xx *rk8xx = raw_to_rk8xx_power(dev)->rk8xx; + + /* Select power-down instead of reset before the platform asserts PWRCTRL. */ + return rt_pin_ctrl_confs_apply_by_name(rk8xx->dev, "pmic-power-off"); +} + static rt_err_t rk8xx_power_off(struct rt_device *dev) { rt_uint32_t reg, bit; @@ -812,7 +820,7 @@ rt_err_t rk8xx_shutdown(struct rk8xx *rk8xx) rt_err_t rk8xx_probe(struct rk8xx *rk8xx) { rt_err_t err; - rt_bool_t iomux_retry; + rt_bool_t iomux_retry, system_power_controller, power_off_pinctrl; rt_bus_t platform_bus; int rk8xx_ep_nr, pre_init_regs_nr; struct rt_ofw_node *np, *dev_np; @@ -967,7 +975,10 @@ rt_err_t rk8xx_probe(struct rk8xx *rk8xx) iomux_retry = rt_pin_ctrl_confs_apply_by_name(rk8xx->dev, RT_NULL) < 0; - if (rt_ofw_prop_read_bool(dev_np, "rockchip,system-power-controller")) + system_power_controller = rt_ofw_prop_read_bool(dev_np, "rockchip,system-power-controller"); + power_off_pinctrl = rk8xx->variant == RK806_ID && rt_pin_ctrl_confs_lookup(rk8xx->dev, "pmic-power-off") >= 0; + + if (system_power_controller || power_off_pinctrl) { struct rt_device *dev; struct rk8xx_power *rk8xx_power = rt_calloc(1, sizeof(*rk8xx_power)); @@ -983,19 +994,23 @@ rt_err_t rk8xx_probe(struct rk8xx *rk8xx) rk8xx_power->rk8xx = rk8xx; err = rt_dm_power_off_handler(dev, RT_DM_POWER_OFF_MODE_SHUTDOWN, - RT_DM_POWER_OFF_PRIO_PLATFORM, &rk8xx_power_off); + RT_DM_POWER_OFF_PRIO_PLATFORM, + power_off_pinctrl ? &rk806_power_off_prepare : &rk8xx_power_off); if (err) { LOG_E("Add %s failed", "shutdown"); } - err = rt_dm_power_off_handler(dev, RT_DM_POWER_OFF_MODE_RESET, - RT_DM_POWER_OFF_PRIO_PLATFORM, &rk8xx_restart); - - if (err) + if (system_power_controller) { - LOG_E("Add %s failed", "reset"); + err = rt_dm_power_off_handler(dev, RT_DM_POWER_OFF_MODE_RESET, + RT_DM_POWER_OFF_PRIO_PLATFORM, &rk8xx_restart); + + if (err) + { + LOG_E("Add %s failed", "reset"); + } } } diff --git a/bsp/rockchip/dm/phye/Kconfig b/bsp/rockchip/dm/phye/Kconfig index ae7cc7ddc7f5..efe0eeba4ee1 100755 --- a/bsp/rockchip/dm/phye/Kconfig +++ b/bsp/rockchip/dm/phye/Kconfig @@ -24,3 +24,21 @@ config RT_PHYE_ROCKCHIP_INNO_DSIDPHY depends on RT_USING_PHYE depends on RT_USING_CLK default n + +config RT_PHYE_ROCKCHIP_HDPTX_HDMI + bool "Rockchip Samsung HDPTX HDMI PHY (rk3588)" + depends on RT_USING_PHYE + depends on RT_USING_CLK + depends on RT_MFD_SYSCON + default n + +config RT_PHYE_ROCKCHIP_INNO_HDMI + bool "Rockchip Innosilicon HDMI PHY (rk3528)" + depends on RT_USING_PHYE + depends on RT_USING_CLK + depends on RT_USING_PIN + depends on RT_MFD_SYSCON + default n + help + RK3528 HDMI PHY and clk_hdmiphy_pixel_io provider. + Supports table-based 8-bit RGB TMDS clocks up to 297 MHz. diff --git a/bsp/rockchip/dm/phye/SConscript b/bsp/rockchip/dm/phye/SConscript index 03aaec0eae14..107fc1c4b410 100755 --- a/bsp/rockchip/dm/phye/SConscript +++ b/bsp/rockchip/dm/phye/SConscript @@ -19,6 +19,12 @@ if GetDepend(['RT_PHYE_ROCKCHIP_MIPI_DCPHY']): if GetDepend(['RT_PHYE_ROCKCHIP_INNO_DSIDPHY']): src += ['phye-rockchip-inno-dsidphy.c'] +if GetDepend(['RT_PHYE_ROCKCHIP_HDPTX_HDMI']): + src += ['phye-rockchip-hdptx-hdmi.c'] + +if GetDepend(['RT_PHYE_ROCKCHIP_INNO_HDMI']): + src += ['phye-rockchip-inno-hdmi.c'] + group = DefineGroup('DeviceDrivers', src, depend = [''], CPPPATH = CPPPATH) Return('group') diff --git a/bsp/rockchip/dm/phye/phye-rockchip-hdptx-hdmi.c b/bsp/rockchip/dm/phye/phye-rockchip-hdptx-hdmi.c new file mode 100644 index 000000000000..3851f512ef03 --- /dev/null +++ b/bsp/rockchip/dm/phye/phye-rockchip-hdptx-hdmi.c @@ -0,0 +1,863 @@ +/* + * Copyright (c) 2006-2026, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2026-3-15 GuEe-GUI the first version + */ + +#include +#include + +#define DBG_TAG "phye.hdptx" +#define DBG_LVL DBG_INFO +#include + +#define HDPTX_TMDS_MAX_RATE 594000000ULL + +#define GRF_HDPTX_CON0 0x0000 +#define GRF_HDPTX_STATUS 0x0080 +#define HDPTX_I_PLL_EN RT_BIT(7) +#define HDPTX_I_BIAS_EN RT_BIT(6) +#define HDPTX_I_BGR_EN RT_BIT(5) +#define HDPTX_O_PLL_LOCK_DONE RT_BIT(3) +#define HDPTX_O_PHY_CLK_RDY RT_BIT(2) +#define HDPTX_O_PHY_RDY RT_BIT(1) + +#define LNTOP_REG0200 0x0800 +#define LNTOP_REG0207 0x081c +#define LANE0_BASE 0x0c00 + +struct hdptx_regval +{ + rt_uint16_t reg; + rt_uint8_t val; +}; + +struct rk_hdptx +{ + struct rt_phye parent; + void *regs; + struct rt_syscon *grf; + struct rt_clk *ref_clk; + struct rt_clk *apb_clk; + struct rt_reset_control *phy_rst; + struct rt_reset_control *apb_rst; + struct rt_reset_control *init_rst; + struct rt_reset_control *cmn_rst; + struct rt_reset_control *lane_rst; + struct rt_reset_control *ropll_rst; + rt_uint64_t tmds_clock; + struct rt_clk_node clk_np; + struct rt_clk_cell pixel_clk; + struct rt_clk_cell *clk_cells[1]; + struct rt_device clk_port_dev; + rt_uint32_t pixel_users; + rt_bool_t pll_configured; + rt_uint8_t id; +}; + +struct hdptx_ropll_cfg +{ + rt_uint8_t pms_mdiv; + rt_uint8_t pms_mdiv_afc; + rt_uint8_t pms_pdiv; + rt_uint8_t pms_refdiv; + rt_uint8_t pms_sdiv; + rt_uint8_t sdm_en; + rt_uint8_t sdm_deno; + rt_uint8_t sdm_num_sign; + rt_uint8_t sdm_num; + rt_uint8_t sdc_n; + rt_uint8_t sdc_num; + rt_uint8_t sdc_deno; +}; + +struct hdptx_ropll_rate_cfg +{ + rt_uint64_t rate; + struct hdptx_ropll_cfg cfg; +}; + +/* + * The HDPTX fractional loop has rate-specific settings. A PLL lock alone + * does not validate the resulting TMDS bit clock, so keep the Linux-verified + * 74.25 MHz entry used by the default 720p mode instead of deriving it. + */ +static const struct hdptx_ropll_rate_cfg hdptx_ropll_rate_cfgs[] = +{ + { + 74250000ULL, + { 124, 124, 1, 1, 7, 1, 62, 1, 16, 5, 0, 1 }, + }, +}; + +#define phye_to_hdptx(phye) rt_container_of(phye, struct rk_hdptx, parent) + +static const struct hdptx_regval hdptx_ropll_common[] = +{ + { 0x0020, 0x00 }, { 0x0024, 0x0c }, { 0x0028, 0x83 }, + { 0x002c, 0x06 }, { 0x0030, 0x20 }, { 0x0034, 0xb8 }, + { 0x0038, 0x0f }, { 0x003c, 0x0f }, { 0x0040, 0x04 }, + { 0x0044, 0x01 }, { 0x0048, 0x26 }, { 0x004c, 0x22 }, + { 0x0050, 0x24 }, { 0x0054, 0x77 }, { 0x0058, 0x08 }, + { 0x005c, 0x20 }, { 0x0060, 0x04 }, { 0x0064, 0x48 }, + { 0x0068, 0x01 }, { 0x006c, 0x00 }, { 0x0070, 0x01 }, + { 0x0074, 0x64 }, { 0x0078, 0x14 }, { 0x007c, 0x00 }, + { 0x0080, 0x00 }, { 0x0084, 0x00 }, { 0x0088, 0x11 }, + { 0x008c, 0x00 }, { 0x0090, 0x00 }, { 0x0094, 0x53 }, + { 0x0098, 0x00 }, { 0x009c, 0x00 }, { 0x00a0, 0x01 }, + { 0x00a4, 0x01 }, { 0x00a8, 0x00 }, { 0x00ac, 0x00 }, + { 0x00b0, 0x00 }, { 0x00b4, 0x00 }, { 0x00b8, 0x04 }, + { 0x00bc, 0x00 }, { 0x00c0, 0x20 }, { 0x00c4, 0x30 }, + { 0x00c8, 0x0b }, { 0x00cc, 0x23 }, { 0x00d0, 0x00 }, + { 0x00d4, 0x00 }, { 0x00e0, 0x00 }, { 0x00e4, 0x00 }, + { 0x00e8, 0x00 }, { 0x00ec, 0x00 }, { 0x00f0, 0x80 }, + { 0x00f4, 0x40 }, { 0x00f8, 0x0c }, { 0x00fc, 0x83 }, + { 0x0100, 0x06 }, { 0x0104, 0x20 }, { 0x0108, 0x78 }, + { 0x010c, 0x00 }, { 0x0110, 0x46 }, { 0x0114, 0x24 }, + { 0x0118, 0xdd }, { 0x011c, 0x00 }, { 0x0120, 0x11 }, + { 0x0124, 0xfa }, { 0x0128, 0x08 }, { 0x012c, 0x00 }, + { 0x0130, 0x01 }, { 0x0134, 0x64 }, { 0x0138, 0x34 }, + { 0x013c, 0x00 }, { 0x0140, 0x00 }, + { 0x0170, 0x25 }, { 0x0174, 0x0c }, { 0x0178, 0x4f }, + { 0x017c, 0x01 }, { 0x01ac, 0x04 }, { 0x01cc, 0x30 }, + { 0x01d0, 0x04 }, { 0x01d4, 0x20 }, { 0x01d8, 0x30 }, + { 0x01dc, 0x08 }, { 0x01e0, 0x0c }, { 0x01e4, 0x00 }, + { 0x01ec, 0x00 }, { 0x01f0, 0x00 }, { 0x01f4, 0x00 }, + { 0x01f8, 0x00 }, { 0x01fc, 0x00 }, { 0x0200, 0x00 }, + { 0x0204, 0x01 }, { 0x0208, 0x04 }, { 0x020c, 0x24 }, + { 0x0210, 0x20 }, { 0x0214, 0x03 }, { 0x021c, 0x04 }, + { 0x0224, 0x00 }, { 0x0228, 0x55 }, { 0x022c, 0x25 }, + { 0x0230, 0x2c }, { 0x0234, 0x22 }, { 0x0238, 0x14 }, + { 0x023c, 0x20 }, { 0x0240, 0x00 }, { 0x0244, 0x00 }, + { 0x0248, 0x00 }, { 0x024c, 0x00 }, { 0x0254, 0x00 }, + { 0x025c, 0x02 }, { 0x0264, 0x04 }, { 0x0268, 0x11 }, + { 0x026c, 0x00 }, +}; + +rt_inline void hdptx_write(struct rk_hdptx *hdptx, rt_uint16_t reg, rt_uint8_t val) +{ + HWREG32((rt_uint8_t *)hdptx->regs + reg) = val; +} + +static void hdptx_update_bits(struct rk_hdptx *hdptx, rt_uint16_t reg, + rt_uint8_t mask, rt_uint8_t val) +{ + rt_uint8_t old; + + old = HWREG32((rt_uint8_t *)hdptx->regs + reg); + + hdptx_write(hdptx, reg, (old & ~mask) | (val & mask)); +} + +static void hdptx_grf_write(struct rk_hdptx *hdptx, rt_uint32_t reg, rt_uint32_t val) +{ + rt_syscon_write(hdptx->grf, reg, val); +} + +static rt_uint32_t hdptx_grf_read(struct rk_hdptx *hdptx, rt_uint32_t reg) +{ + rt_uint32_t val; + + val = 0; + rt_syscon_read(hdptx->grf, reg, &val); + + return val; +} + +static void hdptx_rational_best(rt_uint64_t num, rt_uint64_t den, + rt_uint32_t max_num, rt_uint32_t max_den, rt_uint8_t *best_num, + rt_uint8_t *best_den) +{ + rt_uint64_t best_err = ~(rt_uint64_t)0; + rt_uint32_t n; + rt_uint32_t d; + + *best_num = 0; + *best_den = 1; + + for (d = 1; d <= max_den; ++d) + { + rt_uint64_t err; + + n = (rt_uint32_t)((num * d + den / 2) / den); + if (n > max_num) + { + continue; + } + + err = num * d > den * n ? num * d - den * n : den * n - num * d; + if (err * *best_den < best_err * d) + { + best_err = err; + *best_num = n; + *best_den = d; + } + } +} + +static rt_bool_t hdptx_ropll_calc(rt_uint64_t rate, struct hdptx_ropll_cfg *cfg) +{ + const rt_uint64_t fout = rate / 200; + const rt_uint32_t fref = 24000; + rt_uint32_t sdiv; + rt_uint32_t mdiv = 0; + rt_uint32_t sdc = 0; + rt_uint64_t fvco = 0; + rt_uint8_t k = 0; + rt_uint8_t lc = 1; + rt_uint8_t k_sub = 0; + rt_uint8_t lc_sub = 1; + + if (!fout) + { + return RT_FALSE; + } + + for (sdiv = 16; sdiv; --sdiv) + { + if ((sdiv & 1) && sdiv != 1) + { + continue; + } + + fvco = fout * sdiv; + if (fvco < 2000000 || fvco > 4000000) + { + continue; + } + + mdiv = (rt_uint32_t)((fvco + fref - 1) / fref); + if (mdiv < 20 || mdiv > 255) + { + continue; + } + + if (fref * mdiv != fvco) + { + for (sdc = 264000; sdc <= 750000; sdc += fref) + { + if ((rt_uint64_t)sdc * 8 > (rt_uint64_t)fref * mdiv) + { + break; + } + } + if (sdc > 750000) + { + continue; + } + + hdptx_rational_best((rt_uint64_t)fref * mdiv - fvco, sdc / 16, + 0x7f, 0xff, &k, &lc); + hdptx_rational_best((rt_uint64_t)sdc * 8 - (rt_uint64_t)fref * mdiv, + sdc, 0x7f, 0xff, &k_sub, &lc_sub); + } + break; + } + + if (!sdiv) + { + return RT_FALSE; + } + + cfg->pms_mdiv = mdiv; + cfg->pms_mdiv_afc = mdiv; + cfg->pms_pdiv = 1; + cfg->pms_refdiv = 1; + cfg->pms_sdiv = sdiv - 1; + cfg->sdm_en = k ? 1 : 0; + cfg->sdm_deno = lc; + cfg->sdm_num_sign = cfg->sdm_en; + cfg->sdm_num = k; + cfg->sdc_n = 5; + cfg->sdc_num = k_sub; + cfg->sdc_deno = lc_sub; + + return RT_TRUE; +} + +static rt_bool_t hdptx_ropll_get_cfg(rt_uint64_t rate, struct hdptx_ropll_cfg *cfg) +{ + rt_size_t i; + + for (i = 0; i < RT_ARRAY_SIZE(hdptx_ropll_rate_cfgs); ++i) + { + if (hdptx_ropll_rate_cfgs[i].rate == rate) + { + *cfg = hdptx_ropll_rate_cfgs[i].cfg; + return RT_TRUE; + } + } + + return hdptx_ropll_calc(rate, cfg); +} + +static void hdptx_reset_assert(struct rt_reset_control *rst) +{ + if (!rt_is_err_or_null(rst)) + { + rt_reset_control_assert(rst); + } +} + +static void hdptx_reset_deassert(struct rt_reset_control *rst) +{ + if (!rt_is_err_or_null(rst)) + { + rt_reset_control_deassert(rst); + } +} + +static void hdptx_pre_power_up(struct rk_hdptx *hdptx) +{ + hdptx_reset_assert(hdptx->apb_rst); + rt_hw_us_delay(20); + hdptx_reset_deassert(hdptx->apb_rst); + + hdptx_reset_assert(hdptx->lane_rst); + hdptx_reset_assert(hdptx->cmn_rst); + hdptx_reset_assert(hdptx->init_rst); + hdptx_grf_write(hdptx, GRF_HDPTX_CON0, + (HDPTX_I_PLL_EN | HDPTX_I_BIAS_EN | HDPTX_I_BGR_EN) << 16); +} + +static rt_err_t hdptx_enable_pll(struct rk_hdptx *hdptx) +{ + rt_uint32_t val; + int i; + + hdptx_grf_write(hdptx, GRF_HDPTX_CON0, + ((HDPTX_I_BIAS_EN | HDPTX_I_BGR_EN) << 16) | + HDPTX_I_BIAS_EN | HDPTX_I_BGR_EN); + rt_hw_us_delay(10); + hdptx_reset_deassert(hdptx->init_rst); + rt_hw_us_delay(10); + hdptx_grf_write(hdptx, GRF_HDPTX_CON0, (HDPTX_I_PLL_EN << 16) | HDPTX_I_PLL_EN); + rt_hw_us_delay(10); + hdptx_reset_deassert(hdptx->cmn_rst); + + for (i = 0; i < 20; ++i) + { + val = hdptx_grf_read(hdptx, GRF_HDPTX_STATUS); + if (val & HDPTX_O_PHY_CLK_RDY) + { + return RT_EOK; + } + rt_hw_us_delay(20); + } + + return -RT_ETIMEOUT; +} + +static rt_err_t hdptx_program_ropll(struct rk_hdptx *hdptx, rt_uint64_t rate) +{ + struct hdptx_ropll_cfg cfg; + rt_size_t i; + rt_err_t err; + + if (rate > HDPTX_TMDS_MAX_RATE || !hdptx_ropll_get_cfg(rate, &cfg)) + { + return -RT_EINVAL; + } + + hdptx_pre_power_up(hdptx); + + /* The TMDS ROPLL has its own reset line in the RK3588 binding. */ + hdptx_reset_assert(hdptx->ropll_rst); + rt_hw_us_delay(20); + hdptx_reset_deassert(hdptx->ropll_rst); + hdptx_grf_write(hdptx, GRF_HDPTX_CON0, RT_BIT(11) << 16); + + for (i = 0; i < RT_ARRAY_SIZE(hdptx_ropll_common); ++i) + { + hdptx_write(hdptx, hdptx_ropll_common[i].reg, hdptx_ropll_common[i].val); + } + + hdptx_write(hdptx, 0x0144, cfg.pms_mdiv); + hdptx_write(hdptx, 0x0154, cfg.pms_mdiv_afc); + hdptx_write(hdptx, 0x0164, (cfg.pms_pdiv << 4) | cfg.pms_refdiv); + hdptx_write(hdptx, 0x0168, cfg.pms_sdiv << 4); + hdptx_write(hdptx, 0x0178, cfg.sdm_en ? 0x4f : 0x40); + hdptx_write(hdptx, 0x0180, cfg.sdm_deno); + /* Linux only updates ROPLL_SDM_NUM_SIGN_RBR (bit 3). The reset + * defaults in bits [2:0] are required by the TMDS serial datapath. */ + hdptx_update_bits(hdptx, 0x0190, RT_BIT(3), + cfg.sdm_num_sign ? RT_BIT(3) : 0); + hdptx_write(hdptx, 0x0194, cfg.sdm_num); + hdptx_write(hdptx, 0x01a4, cfg.sdc_n); + hdptx_write(hdptx, 0x01b0, cfg.sdc_num); + hdptx_write(hdptx, 0x01c0, cfg.sdc_deno); + hdptx_write(hdptx, 0x0218, (cfg.pms_sdiv << 4) | RT_BIT(0)); + + err = hdptx_enable_pll(hdptx); + if (!err) + { + hdptx->tmds_clock = rate; + hdptx->pll_configured = RT_TRUE; + } + + return err; +} + +static rt_err_t hdptx_enable_lanes(struct rk_hdptx *hdptx) +{ + rt_uint32_t val; + int i; + + hdptx_reset_deassert(hdptx->lane_rst); + hdptx_grf_write(hdptx, GRF_HDPTX_CON0, + ((HDPTX_I_BIAS_EN | HDPTX_I_BGR_EN) << 16) | + HDPTX_I_BIAS_EN | HDPTX_I_BGR_EN); + hdptx_write(hdptx, LNTOP_REG0207, 0x0f); + + for (i = 0; i < 50; ++i) + { + val = hdptx_grf_read(hdptx, GRF_HDPTX_STATUS); + if ((val & (HDPTX_O_PHY_RDY | HDPTX_O_PLL_LOCK_DONE)) == + (HDPTX_O_PHY_RDY | HDPTX_O_PLL_LOCK_DONE)) + { + return RT_EOK; + } + rt_hw_us_delay(100); + } + + return -RT_ETIMEOUT; +} + +static void hdptx_program_lanes(struct rk_hdptx *hdptx) +{ + static const struct hdptx_regval lane_cfg[] = + { + { 0x000c, 0x0c }, { 0x001c, 0x20 }, { 0x0028, 0x17 }, + { 0x002c, 0x77 }, { 0x0030, 0x77 }, { 0x0034, 0x77 }, + { 0x0038, 0x38 }, { 0x0040, 0x03 }, { 0x0044, 0x0f }, + { 0x0048, 0x00 }, { 0x0058, 0x02 }, { 0x006c, 0x01 }, + { 0x007c, 0x15 }, { 0x0080, 0xa0 }, + }; + rt_size_t i; + rt_uint32_t lane; + + hdptx_write(hdptx, 0x0450, 0x00); + hdptx_write(hdptx, 0x0454, 0x00); + hdptx_write(hdptx, 0x0458, 0x00); + hdptx_write(hdptx, 0x045c, 0x00); + hdptx_write(hdptx, LNTOP_REG0200, 0x06); + hdptx_write(hdptx, 0x0804, 0x07); + hdptx_write(hdptx, 0x0808, 0xc1); + hdptx_write(hdptx, 0x080c, 0xf0); + hdptx_write(hdptx, 0x0810, 0x7c); + hdptx_write(hdptx, 0x0814, 0x1f); + hdptx_write(hdptx, 0x0818, 0x07); + + for (lane = 0; lane < 4; ++lane) + { + for (i = 0; i < RT_ARRAY_SIZE(lane_cfg); ++i) + { + hdptx_write(hdptx, LANE0_BASE + lane * 0x400 + lane_cfg[i].reg, + lane_cfg[i].val); + } + hdptx_write(hdptx, LANE0_BASE + lane * 0x400 + 0x0078, + lane == 3 ? 0x08 : 0x00); + hdptx_write(hdptx, LANE0_BASE + lane * 0x400 + 0x000c, 0x2f); + hdptx_write(hdptx, LANE0_BASE + lane * 0x400 + 0x0014, 0x03); + hdptx_write(hdptx, LANE0_BASE + lane * 0x400 + 0x0018, 0x1c); + } + + /* + * Keep the TMDS lane deskew sequence identical to the RK3588 Linux + * HDPTX driver. A PLL/lane-ready indication alone is not sufficient: + * without these final inter-pair values the link can lock while pixel + * symbols are sampled on the wrong lane boundary. + */ + hdptx_write(hdptx, LANE0_BASE + 0x0078, 0x02); + hdptx_write(hdptx, LANE0_BASE + 0x0400 + 0x0078, 0x02); + hdptx_write(hdptx, LANE0_BASE + 0x0800 + 0x0078, 0x02); + hdptx_write(hdptx, LANE0_BASE + 0x0c00 + 0x0078, 0x0a); +} + +static rt_err_t hdptx_configure(struct rt_phye *phye, + union rt_phye_configure_opts *opts) +{ + struct rk_hdptx *hdptx = phye_to_hdptx(phye); + + if (phye->mode != RT_PHYE_MODE_HDMI || !opts || + !opts->hdmi.pixel_clock || + opts->hdmi.pixel_clock != opts->hdmi.tmds_clock || + opts->hdmi.tmds_clock > HDPTX_TMDS_MAX_RATE) + { + return -RT_EINVAL; + } + + hdptx->tmds_clock = opts->hdmi.tmds_clock; + + return RT_EOK; +} + +static rt_err_t hdptx_init(struct rt_phye *phye) +{ + struct rk_hdptx *hdptx = phye_to_hdptx(phye); + rt_err_t err; + + err = rt_clk_prepare_enable(hdptx->apb_clk); + if (err) + { + return err; + } + + err = rt_clk_prepare_enable(hdptx->ref_clk); + if (err) + { + rt_clk_disable_unprepare(hdptx->apb_clk); + } + + return err; +} + +static rt_err_t hdptx_exit(struct rt_phye *phye) +{ + struct rk_hdptx *hdptx = phye_to_hdptx(phye); + + rt_clk_disable_unprepare(hdptx->ref_clk); + rt_clk_disable_unprepare(hdptx->apb_clk); + + return RT_EOK; +} + +static rt_err_t hdptx_power_on(struct rt_phye *phye) +{ + struct rk_hdptx *hdptx = phye_to_hdptx(phye); + rt_err_t err; + + if (phye->mode != RT_PHYE_MODE_HDMI || !hdptx->tmds_clock) + { + return -RT_EINVAL; + } + + /* + * Match the Linux split: clk_hdmiphy_pixel* programs the ROPLL before + * the HDMI PHY is powered, while power_on only configures TMDS lanes. + * Keep a fallback for consumers which did not enable that clock. + */ + if (!hdptx->pll_configured) + { + err = hdptx_program_ropll(hdptx, hdptx->tmds_clock); + if (err) + { + LOG_E("ROPLL lock timeout"); + return err; + } + } + + hdptx_program_lanes(hdptx); + err = hdptx_enable_lanes(hdptx); + if (err) + { + LOG_E("TMDS lane lock timeout"); + return err; + } + + LOG_D("TMDS locked at %u Hz", (rt_uint32_t)hdptx->tmds_clock); + + return RT_EOK; +} + +static rt_err_t hdptx_power_off(struct rt_phye *phye) +{ + struct rk_hdptx *hdptx = phye_to_hdptx(phye); + + hdptx_write(hdptx, LNTOP_REG0207, 0); + hdptx_reset_assert(hdptx->lane_rst); + if (!hdptx->pixel_users) + { + hdptx_reset_assert(hdptx->cmn_rst); + hdptx_reset_assert(hdptx->init_rst); + hdptx_grf_write(hdptx, GRF_HDPTX_CON0, + (HDPTX_I_PLL_EN | HDPTX_I_BIAS_EN | HDPTX_I_BGR_EN) << 16); + hdptx->pll_configured = RT_FALSE; + } + + return RT_EOK; +} + +static rt_ubase_t hdptx_pixel_recalc_rate(struct rt_clk_cell *cell, + rt_ubase_t parent_rate) +{ + struct rk_hdptx *hdptx = rt_container_of(cell->clk_np, + struct rk_hdptx, clk_np); + + RT_UNUSED(parent_rate); + + return (rt_ubase_t)hdptx->tmds_clock; +} + +static rt_base_t hdptx_pixel_round_rate(struct rt_clk_cell *cell, + rt_ubase_t rate, rt_ubase_t *parent_rate) +{ + struct hdptx_ropll_cfg cfg; + + RT_UNUSED(cell); + RT_UNUSED(parent_rate); + + return rate <= HDPTX_TMDS_MAX_RATE && hdptx_ropll_get_cfg(rate, &cfg) ? + (rt_base_t)rate : -RT_EINVAL; +} + +static rt_err_t hdptx_pixel_set_rate(struct rt_clk_cell *cell, + rt_ubase_t rate, rt_ubase_t parent_rate) +{ + struct rk_hdptx *hdptx = rt_container_of(cell->clk_np, + struct rk_hdptx, clk_np); + + RT_UNUSED(parent_rate); + + if (rate > HDPTX_TMDS_MAX_RATE || !rate) + { + return -RT_EINVAL; + } + + hdptx->tmds_clock = rate; + if (hdptx->pixel_users) + { + return hdptx_program_ropll(hdptx, rate); + } + + return RT_EOK; +} + +static rt_err_t hdptx_pixel_enable(struct rt_clk_cell *cell) +{ + struct rk_hdptx *hdptx = rt_container_of(cell->clk_np, + struct rk_hdptx, clk_np); + rt_err_t err; + + if (hdptx->pixel_users++) + { + return RT_EOK; + } + + err = rt_clk_prepare_enable(hdptx->apb_clk); + if (!err) + { + err = rt_clk_prepare_enable(hdptx->ref_clk); + } + if (!err) + { + err = hdptx_program_ropll(hdptx, hdptx->tmds_clock); + } + if (!err) + { + return RT_EOK; + } + + hdptx->pixel_users = 0; + rt_clk_disable_unprepare(hdptx->ref_clk); + rt_clk_disable_unprepare(hdptx->apb_clk); + + return err; +} + +static void hdptx_pixel_disable(struct rt_clk_cell *cell) +{ + struct rk_hdptx *hdptx = rt_container_of(cell->clk_np, + struct rk_hdptx, clk_np); + + if (!hdptx->pixel_users || --hdptx->pixel_users) + { + return; + } + + hdptx_reset_assert(hdptx->lane_rst); + hdptx_reset_assert(hdptx->cmn_rst); + hdptx_reset_assert(hdptx->init_rst); + hdptx_grf_write(hdptx, GRF_HDPTX_CON0, + (HDPTX_I_PLL_EN | HDPTX_I_BIAS_EN | HDPTX_I_BGR_EN) << 16); + hdptx->pll_configured = RT_FALSE; + rt_clk_disable_unprepare(hdptx->ref_clk); + rt_clk_disable_unprepare(hdptx->apb_clk); +} + +static const struct rt_clk_ops hdptx_pixel_clk_ops = +{ + .enable = hdptx_pixel_enable, + .disable = hdptx_pixel_disable, + .recalc_rate = hdptx_pixel_recalc_rate, + .round_rate = hdptx_pixel_round_rate, + .set_rate = hdptx_pixel_set_rate, +}; + +static rt_err_t hdptx_register_pixel_clk(struct rk_hdptx *hdptx, + struct rt_ofw_node *port_np) +{ + const char *name; + + name = hdptx->id ? "clk_hdmiphy_pixel1" : "clk_hdmiphy_pixel0"; + + hdptx->clk_cells[0] = &hdptx->pixel_clk; + hdptx->pixel_clk.name = name; + hdptx->pixel_clk.ops = &hdptx_pixel_clk_ops; + hdptx->pixel_clk.parents_nr = 1; + hdptx->pixel_clk.parent = hdptx->ref_clk; + hdptx->pixel_clk.parent_name = hdptx->ref_clk->cell->name; + hdptx->pixel_clk.flags = RT_CLK_F_GET_RATE_NOCACHE; + + hdptx->clk_port_dev.ofw_node = port_np; + hdptx->clk_np.dev = &hdptx->clk_port_dev; + hdptx->clk_np.cells_nr = RT_ARRAY_SIZE(hdptx->clk_cells); + hdptx->clk_np.cells = hdptx->clk_cells; + rt_list_init(&hdptx->clk_np.parent.list); + + return rt_clk_register(&hdptx->clk_np); +} + +static rt_err_t hdptx_set_mode(struct rt_phye *phye, + enum rt_phye_mode mode, int submode) +{ + RT_UNUSED(submode); + + if (mode != RT_PHYE_MODE_HDMI) + { + return -RT_ENOSYS; + } + + phye->mode = mode; + + return RT_EOK; +} + +static const struct rt_phye_ops hdptx_ops = +{ + .init = hdptx_init, + .exit = hdptx_exit, + .power_on = hdptx_power_on, + .power_off = hdptx_power_off, + .set_mode = hdptx_set_mode, + .configure = hdptx_configure, +}; + +static rt_err_t hdptx_probe(struct rt_platform_device *pdev) +{ + struct rt_device *dev = &pdev->parent; + struct rk_hdptx *hdptx; + struct rt_ofw_node *port_np; + rt_err_t err; + + hdptx = rt_calloc(1, sizeof(*hdptx)); + if (!hdptx) + { + return -RT_ENOMEM; + } + + hdptx->regs = rt_ofw_iomap(dev->ofw_node, 0); + hdptx->grf = rt_syscon_find_by_ofw_phandle(dev->ofw_node, "rockchip,grf"); + hdptx->ref_clk = rt_clk_get_by_name(dev, "ref"); + hdptx->apb_clk = rt_clk_get_by_name(dev, "apb"); + if (!hdptx->regs || !hdptx->grf || rt_is_err_or_null(hdptx->ref_clk) || + rt_is_err_or_null(hdptx->apb_clk)) + { + err = -RT_EIO; + goto _fail; + } + + hdptx->phy_rst = rt_reset_control_get_by_name(dev, "phy"); + hdptx->apb_rst = rt_reset_control_get_by_name(dev, "apb"); + hdptx->init_rst = rt_reset_control_get_by_name(dev, "init"); + hdptx->cmn_rst = rt_reset_control_get_by_name(dev, "cmn"); + hdptx->lane_rst = rt_reset_control_get_by_name(dev, "lane"); + hdptx->ropll_rst = rt_reset_control_get_by_name(dev, "ropll"); + hdptx->id = rt_ofw_get_alias_id(dev->ofw_node, "hdptxhdmi"); + if (hdptx->id < 0 || hdptx->id > 1) + { + LOG_E("%s: invalid HDPTX HDMI alias ID %d", + rt_ofw_node_full_name(dev->ofw_node), hdptx->id); + err = -RT_EINVAL; + goto _fail; + } + hdptx->parent.dev = dev; + hdptx->parent.ops = &hdptx_ops; + + port_np = rt_ofw_get_child_by_tag(dev->ofw_node, "clk-port"); + if (!port_np) + { + err = -RT_EIO; + goto _fail; + } + + err = hdptx_register_pixel_clk(hdptx, port_np); + if (err) + { + goto _fail; + } + + err = rt_phye_register(&hdptx->parent); + if (err) + { + goto _fail; + } + + dev->user_data = hdptx; + + return RT_EOK; + +_fail: + if (!rt_is_err_or_null(hdptx->phy_rst)) + { + rt_reset_control_put(hdptx->phy_rst); + } + if (!rt_is_err_or_null(hdptx->apb_rst)) + { + rt_reset_control_put(hdptx->apb_rst); + } + if (!rt_is_err_or_null(hdptx->init_rst)) + { + rt_reset_control_put(hdptx->init_rst); + } + if (!rt_is_err_or_null(hdptx->cmn_rst)) + { + rt_reset_control_put(hdptx->cmn_rst); + } + if (!rt_is_err_or_null(hdptx->lane_rst)) + { + rt_reset_control_put(hdptx->lane_rst); + } + if (!rt_is_err_or_null(hdptx->ropll_rst)) + { + rt_reset_control_put(hdptx->ropll_rst); + } + if (!rt_is_err_or_null(hdptx->ref_clk)) + { + rt_clk_put(hdptx->ref_clk); + } + if (!rt_is_err_or_null(hdptx->apb_clk)) + { + rt_clk_put(hdptx->apb_clk); + } + if (hdptx->regs) + { + rt_iounmap(hdptx->regs); + } + rt_free(hdptx); + + return err; +} + +static const struct rt_ofw_node_id hdptx_ids[] = +{ + { .compatible = "rockchip,rk3588-hdptx-phy-hdmi" }, + { /* sentinel */ } +}; + +static struct rt_platform_driver hdptx_driver = +{ + .name = "phy-rockchip-hdptx-hdmi", + .ids = hdptx_ids, + .probe = hdptx_probe, +}; + +static int hdptx_driver_register(void) +{ + return rt_platform_driver_register(&hdptx_driver); +} +INIT_SUBSYS_EXPORT(hdptx_driver_register); diff --git a/bsp/rockchip/dm/phye/phye-rockchip-inno-dsidphy.c b/bsp/rockchip/dm/phye/phye-rockchip-inno-dsidphy.c index 1451d5ed6368..5cdb63b0cb28 100644 --- a/bsp/rockchip/dm/phye/phye-rockchip-inno-dsidphy.c +++ b/bsp/rockchip/dm/phye/phye-rockchip-inno-dsidphy.c @@ -8,12 +8,8 @@ * 2023-09-23 GuEe-GUI first version */ -#include -#include -#include -#include -#include -#include +#include +#include #define DBG_TAG "phye.inno-dsi" #define DBG_LVL DBG_INFO diff --git a/bsp/rockchip/dm/phye/phye-rockchip-inno-hdmi.c b/bsp/rockchip/dm/phye/phye-rockchip-inno-hdmi.c new file mode 100644 index 000000000000..4ecda9f6bba9 --- /dev/null +++ b/bsp/rockchip/dm/phye/phye-rockchip-inno-hdmi.c @@ -0,0 +1,450 @@ +/* + * Copyright (c) 2006-2026, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2026-3-15 GuEe-GUI the first version + */ + +#include + +#define DBG_TAG "phye.inno-hdmi" +#define DBG_LVL DBG_INFO +#include + +struct inno_pll +{ + rt_ubase_t rate; + rt_uint8_t prediv; + rt_uint16_t fbdiv; + rt_uint8_t tmds_a; + rt_uint8_t tmds_b; + rt_uint8_t tmds_c; + rt_uint8_t pixel_a; + rt_uint8_t pixel_b; + rt_uint8_t pixel_c; + rt_uint8_t pixel_d; + rt_uint32_t frac; +}; + +/* 24 MHz reference, pixel clock == TMDS clock (RGB, 8 bpc). */ +static const struct inno_pll inno_plls[] = +{ + { 27000000, 1, 90, 3, 2, 2, 10, 3, 3, 4, 0 }, + { 40000000, 1, 80, 2, 2, 2, 12, 2, 2, 2, 0 }, + { 59400000, 1, 99, 3, 1, 1, 1, 3, 3, 4, 0 }, + { 65000000, 1, 130, 2, 2, 2, 1, 0, 0, 12, 0 }, + { 71000000, 3, 284, 0, 3, 3, 1, 0, 0, 8, 0 }, + { 74176000, 1, 98, 1, 2, 2, 1, 2, 3, 4, 0xe6ae6b }, + { 74250000, 1, 99, 1, 2, 2, 1, 2, 3, 4, 0 }, + { 83500000, 2, 167, 2, 1, 1, 1, 0, 0, 6, 0 }, + { 85750000, 3, 343, 0, 3, 3, 1, 0, 0, 8, 0 }, + { 88750000, 3, 355, 0, 3, 3, 1, 0, 0, 8, 0 }, + { 108000000, 1, 90, 3, 0, 0, 1, 0, 0, 5, 0 }, + { 119000000, 1, 119, 2, 1, 1, 1, 0, 0, 6, 0 }, + { 148352000, 1, 98, 1, 1, 1, 1, 2, 2, 2, 0xe6ae6b }, + { 148500000, 1, 99, 1, 1, 1, 1, 2, 2, 2, 0 }, + { 162000000, 1, 108, 0, 2, 2, 1, 0, 0, 4, 0 }, + { 296703000, 1, 98, 0, 1, 1, 1, 0, 2, 2, 0xe6ae6b }, + { 297000000, 1, 99, 0, 1, 1, 1, 0, 2, 2, 0 }, +}; + +struct inno_hdmi +{ + struct rt_phye phye; + void *regs; + struct rt_clk *sysclk; + struct rt_clk *refclk; + struct rt_clk *pixel; + struct rt_clk_node clk_np; + struct rt_clk_cell pixel_cell; + struct rt_clk_cell *cells[1]; + struct rt_device clk_dev; + const struct inno_pll *pll; + rt_bool_t clock_on; + rt_bool_t lanes_on; +}; + +static const struct inno_pll *inno_find_pll(rt_ubase_t rate) +{ + rt_size_t i; + + for (i = 0; i < RT_ARRAY_SIZE(inno_plls); ++i) + { + if (inno_plls[i].rate == rate) + { + return &inno_plls[i]; + } + } + return RT_NULL; +} + +static void inno_write(struct inno_hdmi *inno, rt_uint32_t reg, rt_uint8_t val) +{ + HWREG32((rt_uint8_t *)inno->regs + reg * 4) = val; +} + +static rt_uint8_t inno_read(struct inno_hdmi *inno, rt_uint32_t reg) +{ + return HWREG32((rt_uint8_t *)inno->regs + reg * 4); +} + +static void inno_update(struct inno_hdmi *inno, rt_uint32_t reg, + rt_uint8_t mask, rt_uint8_t val) +{ + inno_write(inno, reg, (inno_read(inno, reg) & ~mask) | (val & mask)); +} + +static rt_err_t inno_wait_lock(struct inno_hdmi *inno, rt_uint32_t reg) +{ + unsigned int i; + + for (i = 0; i < 500; ++i) + { + if (inno_read(inno, reg) & 1) + { + return RT_EOK; + } + rt_hw_us_delay(10); + } + LOG_E("PLL lock timeout at register 0x%02x", reg); + return -RT_ETIMEOUT; +} + +static rt_err_t inno_pre_pll(struct inno_hdmi *inno, const struct inno_pll *pll) +{ + inno_update(inno, 0xb0, 4, 4); + inno_write(inno, 0xcc, 0x0f); + inno_update(inno, 0xa0, 3, 0); + inno_write(inno, 0xa1, pll->prediv); + inno_write(inno, 0xa2, (pll->fbdiv >> 8) | (pll->frac ? 0xc0 : 0xf0)); + inno_write(inno, 0xa3, pll->fbdiv); + inno_write(inno, 0xa5, pll->pixel_a | (pll->pixel_b << 5)); + inno_write(inno, 0xa6, pll->pixel_d | (pll->pixel_c << 5)); + inno_write(inno, 0xa4, (pll->tmds_a << 4) | (pll->tmds_b << 2) | pll->tmds_c); + inno_write(inno, 0xd3, pll->frac); + inno_write(inno, 0xd2, pll->frac >> 8); + inno_write(inno, 0xd1, pll->frac >> 16); + return inno_wait_lock(inno, 0xa9); +} + +static void inno_lanes_off(struct inno_hdmi *inno) +{ + inno_write(inno, 0xb2, 0); + inno_write(inno, 0xbe, 0); + inno_update(inno, 0xaa, 1, 1); + inno_write(inno, 0x05, 0); + inno_write(inno, 0x07, 0); + inno->lanes_on = RT_FALSE; +} + +static struct inno_hdmi *inno_from_cell(struct rt_clk_cell *cell) +{ + return rt_container_of(cell->clk_np, struct inno_hdmi, clk_np); +} + +static rt_err_t inno_pixel_enable(struct rt_clk_cell *cell) +{ + struct inno_hdmi *inno = inno_from_cell(cell); + rt_err_t err; + + err = rt_clk_prepare_enable(inno->sysclk); + if (err) + { + return err; + } + /* The clock framework enables our refclk parent before this callback. */ + inno_write(inno, 0x02, 0x81); + inno_lanes_off(inno); + err = inno_pre_pll(inno, inno->pll); + if (err) + { + inno_update(inno, 0xa0, 1, 1); + inno_write(inno, 0xcc, 0); + inno_update(inno, 0xb0, 4, 0); + rt_clk_disable_unprepare(inno->sysclk); + return err; + } + inno->clock_on = RT_TRUE; + return RT_EOK; +} + +static void inno_pixel_disable(struct rt_clk_cell *cell) +{ + struct inno_hdmi *inno = inno_from_cell(cell); + + inno_lanes_off(inno); + inno_update(inno, 0xa0, 1, 1); + inno_write(inno, 0xcc, 0); + inno_update(inno, 0xb0, 4, 0); + inno->clock_on = RT_FALSE; + rt_clk_disable_unprepare(inno->sysclk); +} + +static rt_ubase_t inno_pixel_recalc(struct rt_clk_cell *cell, rt_ubase_t parent_rate) +{ + RT_UNUSED(parent_rate); + return inno_from_cell(cell)->pll->rate; +} + +static rt_base_t inno_pixel_round(struct rt_clk_cell *cell, rt_ubase_t rate, + rt_ubase_t *parent_rate) +{ + RT_UNUSED(cell); + return *parent_rate == 24000000 && inno_find_pll(rate) ? (rt_base_t)rate : -RT_EINVAL; +} + +static rt_err_t inno_pixel_set_rate(struct rt_clk_cell *cell, rt_ubase_t rate, + rt_ubase_t parent_rate) +{ + struct inno_hdmi *inno = inno_from_cell(cell); + const struct inno_pll *pll = inno_find_pll(rate); + rt_err_t err; + + if (!pll || parent_rate != 24000000) + { + return -RT_EINVAL; + } + if (inno->lanes_on && pll != inno->pll) + { + return -RT_EBUSY; + } + if (inno->clock_on && pll != inno->pll) + { + err = inno_pre_pll(inno, pll); + if (err) + { + inno_pre_pll(inno, inno->pll); + return err; + } + } + inno->pll = pll; + return RT_EOK; +} + +static const struct rt_clk_ops inno_pixel_ops = +{ + .enable = inno_pixel_enable, + .disable = inno_pixel_disable, + .recalc_rate = inno_pixel_recalc, + .round_rate = inno_pixel_round, + .set_rate = inno_pixel_set_rate, +}; + +static rt_err_t inno_configure(struct rt_phye *phye, + union rt_phye_configure_opts *opts) +{ + struct inno_hdmi *inno = rt_container_of(phye, struct inno_hdmi, phye); + + if (!opts || opts->hdmi.pixel_clock != opts->hdmi.tmds_clock || + opts->hdmi.pixel_clock > 297000000) + { + return -RT_EINVAL; + } + return rt_clk_set_rate(inno->pixel, opts->hdmi.pixel_clock); +} + +static rt_err_t inno_power_on(struct rt_phye *phye) +{ + struct inno_hdmi *inno = rt_container_of(phye, struct inno_hdmi, phye); + static const rt_uint8_t analog[][9] = + { + { 2, 4, 15, 15, 0, 0x76, 0x83, 0x0a, 0x0a }, + { 2, 11, 15, 15, 0, 0x76, 0x83, 0x0a, 0x33 }, + { 3, 4, 12, 18, 0, 0x76, 0x83, 0, 0x0f }, + }; + const rt_uint8_t *cfg; + rt_uint32_t rate; + rt_uint32_t counter; + rt_uint32_t i; + rt_err_t err; + + rate = inno->pll->rate; + err = rt_clk_prepare_enable(inno->pixel); + + if (err) + { + return err; + } + cfg = analog[rate <= 165000000 ? 0 : rate <= 185625000 ? 1 : 2]; + inno_update(inno, 0xaa, 1, 0); + inno_write(inno, 0xab, rate <= 148500000 ? 1 : 2); + inno_write(inno, 0xad, rate <= 74250000 ? 1 : 0); + inno_write(inno, 0xaa, 0x0e); + inno_write(inno, 0xac, rate > 74250000 && rate <= 148500000 ? 10 : 20); + inno_update(inno, 0xad, 0x10, 0); + inno_write(inno, 0xbf, (cfg[0] << 4) | cfg[1]); + inno_write(inno, 0xc0, (cfg[1] << 4) | cfg[1]); + inno_write(inno, 0xb5, cfg[2]); + for (i = 0; i < 3; ++i) + { + inno_write(inno, 0xb6 + i, cfg[3]); + inno_write(inno, 0xbb + i, cfg[4]); + } + inno_write(inno, 0xb4, 7); + inno_write(inno, 0xbe, 0x70); + inno_write(inno, 0xb2, 0x0f); + err = inno_wait_lock(inno, 0xaf); + if (err) + { + inno_lanes_off(inno); + rt_clk_disable_unprepare(inno->pixel); + return err; + } + inno_write(inno, 0xc7, cfg[5]); + inno_write(inno, 0xc5, cfg[6]); + inno_write(inno, 0xc8, cfg[7]); + for (i = 0; i < 3; ++i) + { + inno_write(inno, 0xc9 + i, cfg[8]); + } + counter = 47520000000ULL / rate; + inno_write(inno, 0xd8, counter >> 8); + inno_write(inno, 0xd9, counter); + inno_update(inno, 0xaa, 1, 0); + inno_update(inno, 0xb0, 4, 4); + inno_write(inno, 0xb2, 0x0f); + inno_update(inno, 0x02, 1, 0); + inno_update(inno, 0x02, 1, 1); + /* No PHY interrupt handler: leave the interrupt sources masked. */ + inno_write(inno, 0x05, 0); + inno_write(inno, 0x07, 0); + inno_write(inno, 0xcc, 0x0f); + inno->lanes_on = RT_TRUE; + LOG_D("RK3528 TMDS locked at %u Hz", rate); + return RT_EOK; +} + +static rt_err_t inno_power_off(struct rt_phye *phye) +{ + struct inno_hdmi *inno = rt_container_of(phye, struct inno_hdmi, phye); + + inno_lanes_off(inno); + rt_clk_disable_unprepare(inno->pixel); + return RT_EOK; +} + +static rt_err_t inno_set_mode(struct rt_phye *phye, + enum rt_phye_mode mode, int submode) +{ + RT_UNUSED(phye); + RT_UNUSED(submode); + return mode == RT_PHYE_MODE_HDMI ? RT_EOK : -RT_EINVAL; +} + +static const struct rt_phye_ops inno_ops = +{ + .configure = inno_configure, + .set_mode = inno_set_mode, + .power_on = inno_power_on, + .power_off = inno_power_off, +}; + +static rt_err_t inno_hdmi_probe(struct rt_platform_device *pdev) +{ + struct rt_device *dev = &pdev->parent; + struct inno_hdmi *inno; + struct rt_ofw_node *port; + rt_err_t err; + + inno = rt_calloc(1, sizeof(*inno)); + port = RT_NULL; + err = -RT_EIO; + + if (!inno) + { + return -RT_ENOMEM; + } + inno->regs = rt_ofw_iomap(dev->ofw_node, 0); + inno->sysclk = rt_clk_get_by_name(dev, "sysclk"); + inno->refclk = rt_clk_get_by_name(dev, "refclk"); + if (!inno->regs || rt_is_err_or_null(inno->sysclk) || + rt_is_err_or_null(inno->refclk)) + { + goto fail; + } + if (rt_clk_get_rate(inno->refclk) != 24000000) + { + err = -RT_EINVAL; + goto fail; + } + port = rt_ofw_get_child_by_tag(dev->ofw_node, "clk-port"); + if (!port) + { + goto fail; + } + inno->pll = inno_find_pll(74250000); + inno->cells[0] = &inno->pixel_cell; + inno->pixel_cell.name = "clk_hdmiphy_pixel_io"; + inno->pixel_cell.ops = &inno_pixel_ops; + inno->pixel_cell.parents_nr = 1; + inno->pixel_cell.parent = inno->refclk; + inno->pixel_cell.parent_name = inno->refclk->cell->name; + inno->pixel_cell.flags = RT_CLK_F_GET_RATE_NOCACHE; + inno->clk_dev.ofw_node = port; + inno->clk_np.dev = &inno->clk_dev; + inno->clk_np.cells = inno->cells; + inno->clk_np.cells_nr = 1; + rt_list_init(&inno->clk_np.parent.list); + err = rt_clk_register(&inno->clk_np); + if (err) + { + goto fail; + } + inno->pixel = rt_clk_cell_get_clk(&inno->pixel_cell, RT_NULL); + if (rt_is_err_or_null(inno->pixel)) + { + err = -RT_ENOMEM; + goto fail_clock; + } + inno->phye.dev = dev; + inno->phye.ops = &inno_ops; + err = rt_phye_register(&inno->phye); + if (err) + { + rt_clk_put(inno->pixel); + goto fail_clock; + } + dev->user_data = inno; + + return RT_EOK; + +fail_clock: + rt_clk_unregister(&inno->clk_np); + if (rt_ofw_data(port) == &inno->clk_np.parent) + { + rt_ofw_data(port) = RT_NULL; + } +fail: + rt_ofw_node_put(port); + if (!rt_is_err_or_null(inno->sysclk)) + { + rt_clk_put(inno->sysclk); + } + if (!rt_is_err_or_null(inno->refclk)) + { + rt_clk_put(inno->refclk); + } + if (inno->regs) + { + rt_iounmap(inno->regs); + } + rt_free(inno); + return err; +} + +static const struct rt_ofw_node_id inno_hdmi_ids[] = +{ + { .compatible = "rockchip,rk3528-hdmi-phy" }, + { /* sentinel */ } +}; + +static struct rt_platform_driver inno_hdmi_driver = +{ + .name = "phy-rockchip-inno-hdmi", + .ids = inno_hdmi_ids, + .probe = inno_hdmi_probe, +}; +RT_PLATFORM_DRIVER_EXPORT(inno_hdmi_driver); diff --git a/bsp/rockchip/dm/phye/phye-rockchip-inno-usb2.c b/bsp/rockchip/dm/phye/phye-rockchip-inno-usb2.c index 2e7e9171afe9..9a4723109841 100644 --- a/bsp/rockchip/dm/phye/phye-rockchip-inno-usb2.c +++ b/bsp/rockchip/dm/phye/phye-rockchip-inno-usb2.c @@ -11,9 +11,6 @@ #include #include -#include -#include - #define DBG_TAG "phye.rockchip.inno-usb2" #define DBG_LVL DBG_INFO #include diff --git a/bsp/rockchip/dm/phye/phye-rockchip-mipi-dcphy.c b/bsp/rockchip/dm/phye/phye-rockchip-mipi-dcphy.c index 7f634821343d..7f0db37a6924 100644 --- a/bsp/rockchip/dm/phye/phye-rockchip-mipi-dcphy.c +++ b/bsp/rockchip/dm/phye/phye-rockchip-mipi-dcphy.c @@ -8,13 +8,8 @@ * 2023-09-23 GuEe-GUI first version */ -#include -#include -#include -#include -#include -#include -#include +#include +#include #define DBG_TAG "phye.dcphy" #define DBG_LVL DBG_INFO @@ -467,7 +462,7 @@ static rt_err_t rk_mipi_dcphy_probe(struct rt_platform_device *pdev) dev->user_data = dcphy; - LOG_I("MIPI DCPHY %s probed", np->name); + LOG_D("MIPI DCPHY %s probed", np->name); return RT_EOK; diff --git a/bsp/rockchip/dm/pmdomain/pm-domain-rockchip.c b/bsp/rockchip/dm/pmdomain/pm-domain-rockchip.c index 7567980bad96..c12f41c99951 100755 --- a/bsp/rockchip/dm/pmdomain/pm-domain-rockchip.c +++ b/bsp/rockchip/dm/pmdomain/pm-domain-rockchip.c @@ -976,7 +976,7 @@ static rt_err_t rockchip_pm_add_one_domain(struct rockchip_pmu *pmu, rk_domain = &pmu->rk_pmdomains[idx]; domain_info = &pmu->info->domain_info[idx]; - if (domain_info->pwr_mask) + if (!domain_info->pwr_mask) { rk_domain->is_ignore_pwr = RT_TRUE; } diff --git a/bsp/rockchip/dm/soc/rga2/rga2_drv.c b/bsp/rockchip/dm/soc/rga2/rga2_drv.c index 594e6429b409..63f1a7d2e9f9 100644 --- a/bsp/rockchip/dm/soc/rga2/rga2_drv.c +++ b/bsp/rockchip/dm/soc/rga2/rga2_drv.c @@ -485,6 +485,7 @@ static rt_err_t rockchip_rga2_remove(struct rt_platform_device *pdev) static const struct rt_ofw_node_id rockchip_rga2_ofw_ids[] = { { .compatible = "rockchip,rga2" }, + { .compatible = "rockchip,rga2_core0" }, { /* sentinel */ } }; diff --git a/bsp/rockchip/rk3500/README.md b/bsp/rockchip/rk3500/README.md index 25e40c20bc51..7e151d9942fe 100644 --- a/bsp/rockchip/rk3500/README.md +++ b/bsp/rockchip/rk3500/README.md @@ -59,6 +59,43 @@ scons ## 3. Execution +### Configuring Framebuffer Memory + +Framebuffer and RGA use DMA pools. Add or adjust the bootargs: + +```text +cma=48M coherent_pool=4M +``` + +`cma` includes `coherent_pool` and must be larger than it. This example +provides approximately 4 MiB of coherent memory and 44 MiB of ordinary CMA memory; +adjust the parameters to the board's memory layout to avoid overlapping device-tree +reserved memory. + +### Memory Configuration by Resolution + +The following estimates assume 32-bit RGB/RGBA double buffering. These are starting +configurations for display; allow additional memory for RGA and other DMA devices. + +| Resolution | Double Framebuffer | Example Boot Arguments | +| ---------- | ------------------ | ---------------------- | +| 1280 x 720 | 7.03 MiB | `cma=16M coherent_pool=4M` | +| 1920 x 1080 | 15.82 MiB | `cma=32M coherent_pool=4M` | +| 2560 x 1440 | 28.13 MiB | `cma=48M coherent_pool=4M` | + +### Inspecting Memory Pools + +Run in the RT-Thread shell: + +```text +list_dma_pool +``` + +If boot logs report `dma-pool ... overlap` or `Alloc DMA pool ... fail`, first check +whether the boot arguments cause a conflict with reserved memory. + +### Booting + The RK3500 SoC uses different methods to install the kernel depending on the board.It is recommended to install and boot from an SD card.The following steps take the Orange Pi Compute Module 4 as an example: Connect the serial port of the development board: diff --git a/bsp/rockchip/rk3500/README_ZH.md b/bsp/rockchip/rk3500/README_ZH.md index 74cfe95b2b14..b55d9795f155 100644 --- a/bsp/rockchip/rk3500/README_ZH.md +++ b/bsp/rockchip/rk3500/README_ZH.md @@ -60,6 +60,39 @@ scons ## 3. 运行 +### 显存如何配置 + +Framebuffer 和 RGA 使用 DMA 内存池。在 bootargs 中调整参数: + +```text +cma=48M coherent_pool=4M +``` + +`cma` 已包含 `coherent_pool`,且必须大于后者。上例划分出约 +4 MiB coherent 池和 44 MiB 普通 CMA 池。参数过大会与设备树保留内存重叠,需按板卡内存布局调整。 + +### 不同分辨率配置的显存 + +以下按 32 位 RGB/RGBA 双缓冲估算,参数为显示用途的起步配置,RGA 和其他 DMA 设备需另留余量。 + +| 分辨率 | 双缓冲显存 | 启动参数示例 | +| ------ | ---------- | ------------ | +| 1280 x 720 | 7.03 MiB | `cma=16M coherent_pool=4M` | +| 1920 x 1080 | 15.82 MiB | `cma=32M coherent_pool=4M` | +| 2560 x 1440 | 28.13 MiB | `cma=48M coherent_pool=4M` | + +### 内存池查看 + +在 RT-Thread 命令行执行: + +```text +list_dma_pool +``` + +若启动日志出现 `dma-pool ... overlap` 或 `Alloc DMA pool ... fail`,应先检查启动参数与保留内存是否冲突。 + +### 启动系统 + RK3500 根据不同的板卡有不同的内核安装方法,建议安装到 SD 卡中,这里以Orange Pi Compute Module 4举例说明: 连接开发板串口: ![uart](./figures/uart.png) diff --git a/components/drivers/include/drivers/phye-hdmi.h b/components/drivers/include/drivers/phye-hdmi.h new file mode 100644 index 000000000000..b9fb5148f0c9 --- /dev/null +++ b/components/drivers/include/drivers/phye-hdmi.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2006-2026, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2026-10-01 GuEe-GUI first version + */ + +#ifndef __PHYE_HDMI_H__ +#define __PHYE_HDMI_H__ + +#include + +struct rt_phye_configure_opts_hdmi +{ + rt_uint64_t pixel_clock; + rt_uint64_t tmds_clock; +}; + +#endif /* __PHYE_HDMI_H__ */ diff --git a/components/drivers/include/drivers/phye.h b/components/drivers/include/drivers/phye.h index 143a7d75e191..0074128b50a6 100644 --- a/components/drivers/include/drivers/phye.h +++ b/components/drivers/include/drivers/phye.h @@ -13,6 +13,7 @@ #include #include +#include #include enum rt_phye_mode @@ -37,6 +38,7 @@ enum rt_phye_mode RT_PHYE_MODE_SATA, RT_PHYE_MODE_LVDS, RT_PHYE_MODE_DP, + RT_PHYE_MODE_HDMI, RT_PHYE_MODE_MAX, @@ -50,6 +52,7 @@ struct rt_phye_ops; union rt_phye_configure_opts { + struct rt_phye_configure_opts_hdmi hdmi; struct rt_phye_configure_opts_mipi_dphy mipi_dphy; };