mirror of
https://github.com/libretro/Lakka-LibreELEC.git
synced 2025-03-14 10:07:20 +00:00
ref: - https://patchwork.kernel.org/project/linux-phy/list/?series=918529 - https://patchwork.freedesktop.org/series/120825/
6738 lines
204 KiB
Diff
6738 lines
204 KiB
Diff
From patchwork Tue Dec 17 06:51:43 2024
|
|
Content-Type: text/plain; charset="utf-8"
|
|
MIME-Version: 1.0
|
|
Content-Transfer-Encoding: 7bit
|
|
Subject: [PATCH v20 0/8] Initial support Cadence MHDP8501(HDMI/DP) for i.MX8MQ
|
|
From: Sandor Yu <Sandor.yu@nxp.com>
|
|
Message-Id: <cover.1734340233.git.Sandor.yu@nxp.com>
|
|
To: dmitry.baryshkov@linaro.org, andrzej.hajda@intel.com,
|
|
neil.armstrong@linaro.org, Laurent.pinchart@ideasonboard.com,
|
|
jonas@kwiboo.se, jernej.skrabec@gmail.com, airlied@gmail.com,
|
|
daniel@ffwll.ch, robh+dt@kernel.org, krzysztof.kozlowski+dt@linaro.org,
|
|
shawnguo@kernel.org, s.hauer@pengutronix.de, festevam@gmail.com,
|
|
vkoul@kernel.org, dri-devel@lists.freedesktop.org,
|
|
devicetree@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
|
|
linux-kernel@vger.kernel.org, linux-phy@lists.infradead.org,
|
|
mripard@kernel.org
|
|
Cc: kernel@pengutronix.de, linux-imx@nxp.com, Sandor.yu@nxp.com,
|
|
oliver.brown@nxp.com, alexander.stein@ew.tq-group.com, sam@ravnborg.org
|
|
Date: Tue, 17 Dec 2024 14:51:42 +0800
|
|
|
|
The patch set initial support Cadence MHDP8501(HDMI/DP) DRM bridge
|
|
driver and Cadence HDP-TX PHY(HDMI/DP) driver for Freescale i.MX8MQ.
|
|
|
|
The patch set compose of DRM bridge drivers and PHY driver.
|
|
|
|
Both of them need by patch #1 and #3 to pass build.
|
|
|
|
DRM bridges driver patches:
|
|
#1: soc: cadence: Create helper functions for Cadence MHDP
|
|
#2: drm: bridge: cadence: Update mhdp8546 mailbox access functions
|
|
#3: phy: Add HDMI configuration options
|
|
#4: dt-bindings: display: bridge: Add Cadence MHDP8501
|
|
#5: drm: bridge: Cadence: Add MHDP8501 DP/HDMI driver
|
|
|
|
PHY driver patches:
|
|
#1: soc: cadence: Create helper functions for Cadence MHDP
|
|
#3: phy: Add HDMI configuration options
|
|
#6: dt-bindings: phy: Add Freescale iMX8MQ DP and HDMI PHY
|
|
#7: phy: freescale: Add DisplayPort/HDMI Combo-PHY driver for i.MX8MQ
|
|
|
|
i.MX8M/TQMa8Mx DT patches:
|
|
#8: Add DT nodes for DCSS/HDMI pipeline
|
|
#9: Enable HDMI for TQMa8Mx/MBa8Mx
|
|
|
|
v19->v20:
|
|
Patch #1: soc: cadence: Create helper functions for Cadence MHDP
|
|
Patch #2: drm: bridge: cadence: Update mhdp8546 mailbox access functions
|
|
- The two patches are split from Patch #1 in v19.
|
|
The MHDP helper functions have been moved in a new "cadence" directory
|
|
under the SOC directory in patch #1, in order to promote code reuse
|
|
among MHDP8546, MHDP8501, and the i.MX8MQ HDMI/DP PHY drivers,
|
|
|
|
Patch #3: phy: Add HDMI configuration options
|
|
- Add a-b tag
|
|
|
|
Patch #4: dt-bindings: display: bridge: Add Cadence MHDP8501
|
|
- remove data type link of data-lanes
|
|
|
|
Patch #5: drm: bridge: Cadence: Add MHDP8501 DP/HDMI driver
|
|
- Dump mhdp FW version by debugfs
|
|
- Combine HDMI and DP cable detect functions into one function
|
|
- Combine HDMI and DP cable bridge_mode_valid() functions into one function
|
|
- Rename cdns_hdmi_reset_link() to cdns_hdmi_handle_hotplug()
|
|
- Add comments for EDID in cdns_hdmi_handle_hotplug() and cdns_dp_check_link_state()
|
|
- Add atomic_get_input_bus_fmts() and bridge_atomic_check() for DP driver
|
|
- Remove bpc and color_fmt init in atomic_enable() function.
|
|
- More detail comments for DDC adapter only support SCDC_I2C_SLAVE_ADDRESS
|
|
read and write in HDMI driver.
|
|
|
|
Patch #7: phy: freescale: Add DisplayPort/HDMI Combo-PHY driver for i.MX8MQ
|
|
- implify DP configuration handling by directly copying
|
|
the configuration options to the driver's internal structure.
|
|
- return the error code directly instead of logging an error message in `hdptx_clk_enable`
|
|
- Remove redundant ref_clk_rate check
|
|
|
|
|
|
v18->v19:
|
|
Patch #1
|
|
- use guard(mutex)
|
|
- Add kerneldocs for all new APIs.
|
|
- Detail comments for mailbox access specific case.
|
|
- remove cdns_mhdp_dp_reg_write() because it is not needed by driver now.
|
|
|
|
Patch #3
|
|
- move property data-lanes to endpoint of port@1
|
|
|
|
Patch #4
|
|
- get endpoint for data-lanes as it had move to endpoint of port@1
|
|
- update clock management as devm_clk_get_enabled() introduced.
|
|
- Fix clear_infoframe() function is not work issue.
|
|
- Manage PHY power state via phy_power_on() and phy_power_off().
|
|
|
|
Patch #6
|
|
- Simplify the PLL table by removing unused and constant data
|
|
- Remove PHY power management, controller driver will handle them.
|
|
- Remove enum dp_link_rate
|
|
- introduce read_pll_timeout.
|
|
- update clock management as devm_clk_get_enabled() introduced.
|
|
- remove cdns_hdptx_phy_init() and cdns_hdptx_phy_remove().
|
|
|
|
Patch #8:
|
|
- move property data-lanes to endpoint of port@1
|
|
|
|
v17->v18:
|
|
Patch #1
|
|
- Create three ordinary mailbox access APIs
|
|
cdns_mhdp_mailbox_send
|
|
cdns_mhdp_mailbox_send_recv
|
|
cdns_mhdp_mailbox_send_recv_multi
|
|
- Create three secure mailbox access APIs
|
|
cdns_mhdp_secure_mailbox_send
|
|
cdns_mhdp_secure_mailbox_send_recv
|
|
cdns_mhdp_secure_mailbox_send_recv_multi
|
|
- MHDP8546 DP and HDCP commands that need access mailbox are rewrited
|
|
with above 6 API functions.
|
|
|
|
Patch #3
|
|
- remove lane-mapping and replace it with data-lanes
|
|
- remove r-b tag as property changed.
|
|
|
|
Patch #4
|
|
- MHDP8501 HDMI and DP commands that need access mailbox are rewrited
|
|
with new API functions created in patch #1.
|
|
- replace lane-mapping with data-lanes, use the value from data-lanes
|
|
to reorder HDMI and DP lane mapping.
|
|
- create I2C adapter for HDMI SCDC, remove cdns_hdmi_scdc_write() function.
|
|
- Rewrite cdns_hdmi_sink_config() function, use HDMI SCDC helper function
|
|
drm_scdc_set_high_tmds_clock_ratio() and drm_scdc_set_scrambling()
|
|
to config HDMI sink TMDS.
|
|
- Remove struct video_info from HDMI driver.
|
|
- Remove tmds_char_rate_valid() be called in bridge_mode_valid(),
|
|
community had patch in reviewing to implement the function.
|
|
- Remove warning message print when get unknown HPD cable status.
|
|
- Add more detail comments for HDP plugin and plugout interrupt.
|
|
- use dev_dbg to repleace DRM_INFO when cable HPD status changed.
|
|
- Remove t-b tag as above code change.
|
|
|
|
Patch #6
|
|
- fix build error as code rebase to latest kernel version.
|
|
|
|
Patch #8:
|
|
- replace lane-mapping with data-lanes
|
|
|
|
|
|
v16->v17:
|
|
Patch #1:
|
|
- Replaces the local mutex mbox_mutex with a global mutex mhdp_mailbox_mutex
|
|
Patch #2:
|
|
- remove hdmi.h
|
|
- add 2024 year to copyright
|
|
- Add r-b tag.
|
|
Patch #3:
|
|
- Add lane-mapping property.
|
|
Patch #4:
|
|
- Reset the HDMI/DP link when an HPD (Hot Plug Detect) event is detected
|
|
- Move the HDMI protocol settings from hdmi_ctrl_init() to a new function
|
|
cdns_hdmi_set_hdmi_mode_type(), to align with the introduced link reset functionality.
|
|
- Implement logic to check the type of HDMI sink.
|
|
If the sink is not a hdmi display, set the default mode to DVI.
|
|
- Implement hdmi_reset_infoframe function
|
|
- Reorder certain bit definitions in the header file to follow a descending order.
|
|
- Add "lane-mapping" property for both HDMI and DP, remove platform data from driver.
|
|
lane-mapping should be setting in dts according different board layout.
|
|
- Remove variable mode in struct cdns_mhdp8501_device, video mode could get from struct drm_crtc_state
|
|
- Remove variable char_rate in struct cdns_mhdp8501_device, it could get from struct struct drm_connector_state.hdmi
|
|
- Replaces the local mutex mbox_mutex with a global mutex mhdp_mailbox_mutex
|
|
- Remove mutext protect for phy_api access functions.
|
|
Patch #6:
|
|
- Remove mbox_mutex
|
|
|
|
v15->v16:
|
|
Patch #2:
|
|
- Remove pixel_clk_rate, bpc and color_space fields from struct
|
|
phy_configure_opts_hdmi, they were replaced by
|
|
unsigned long long tmds_char_rate.
|
|
- Remove r-b and a-c tags because this patch have important change.
|
|
Patch #4:
|
|
- Add DRM_BRIDGE_OP_HDMI flags for HDMI driver,
|
|
- Introduce the hdmi info frame helper functions,
|
|
added hdmi_clear_infoframe(), hdmi_write_infoframe() and
|
|
hdmi_tmds_char_rate_valid() according Dmitry's patch
|
|
'make use of the HDMI connector infrastructure' patchset ([2]).
|
|
- mode_fixup() is replaced by atomic_check().
|
|
- Fix video mode 4Kp30 did not work on some displays that support
|
|
LTE_340Mcsc_scramble.
|
|
- updated for tmds_char_rate added in patch #2.
|
|
Patch #6:
|
|
- updated for tmds_char_rate added in patch #2.
|
|
|
|
v14->v15:
|
|
Patch #6 + #7:
|
|
- Merged PHY driver into a single combo PHY driver
|
|
Patch #7 + #8:
|
|
- Add DT patches for a running HDMI setup
|
|
|
|
v13->v14:
|
|
Patch #4:
|
|
- Rebase to next-20240219, replace get_edid function by edid_read
|
|
function as commits d807ad80d811b ("drm/bridge: add ->edid_read
|
|
hook and drm_bridge_edid_read()") and 27b8f91c08d99 ("drm/bridge:
|
|
remove ->get_edid callback") had change the API.
|
|
|
|
v12->v13:
|
|
Patch #4:
|
|
- Explicitly include linux/platform_device.h for cdns-mhdp8501-core.c
|
|
- Fix build warning
|
|
- Order bit bpc and color_space in descending shit.
|
|
Patch #7:
|
|
- Fix build warning
|
|
|
|
v11->v12:
|
|
Patch #1:
|
|
- Move status initialize out of mbox_mutex.
|
|
- Reorder API functions in alphabetical.
|
|
- Add notes for malibox access functions.
|
|
- Add year 2024 to copyright.
|
|
Patch #4:
|
|
- Replace DRM_INFO with dev_info or dev_warn.
|
|
- Replace DRM_ERROR with dev_err.
|
|
- Return ret when cdns_mhdp_dpcd_read failed in function cdns_dp_aux_transferi().
|
|
- Remove unused parmeter in function cdns_dp_get_msa_misc
|
|
and use two separate variables for color space and bpc.
|
|
- Add year 2024 to copyright.
|
|
Patch #6:
|
|
- Return error code to replace -1 for function wait_for_ack().
|
|
- Set cdns_phy->power_up = false in phy_power_down function.
|
|
- Remove "RATE_8_1 = 810000", it is not used in driver.
|
|
- Add year 2024 to copyright.
|
|
Patch #7:
|
|
- Adjust clk disable order.
|
|
- Return error code to replace -1 for function wait_for_ack().
|
|
- Use bool for variable pclk_in.
|
|
- Add year 2024 to copyright.
|
|
|
|
v10->v11:
|
|
- rewrite cdns_mhdp_set_firmware_active() in mhdp8546 core driver,
|
|
use cdns_mhdp_mailbox_send() to replace cdns_mhdp_mailbox_write()
|
|
same as the other mailbox access functions.
|
|
- use static for cdns_mhdp_mailbox_write() and cdns_mhdp_mailbox_read()
|
|
and remove them from EXPORT_SYMBOL_GPL().
|
|
- remove MODULE_ALIAS() from mhdp8501 driver.
|
|
|
|
v9->v10:
|
|
- Create mhdp helper driver to replace macro functions,
|
|
move all mhdp mailbox access functions and common functions
|
|
into the helper driver.
|
|
Patch #1:drm: bridge: Cadence: Creat mhdp helper driver
|
|
it is totaly different with v9.
|
|
|
|
v8->v9:
|
|
- Remove compatible string "cdns,mhdp8501" that had removed
|
|
from dt-bindings file in v8.
|
|
- Add Dmitry's R-b tag to patch #2
|
|
- Add Krzysztof's R-b tag to patch #3
|
|
|
|
v7->v8:
|
|
MHDP8501 HDMI/DP:
|
|
- Correct DT node name to "display-bridge".
|
|
- Remove "cdns,mhdp8501" from mhdp8501 dt-binding doc.
|
|
|
|
HDMI/DP PHY:
|
|
- Introduced functions `wait_for_ack` and `wait_for_ack_clear` to handle
|
|
waiting with acknowledgment bits set and cleared respectively.
|
|
- Use FIELD_PRE() to set bitfields for both HDMI and DP PHY.
|
|
|
|
v6->v7:
|
|
MHDP8501 HDMI/DP:
|
|
- Combine HDMI and DP driver into one mhdp8501 driver.
|
|
Use the connector type to load the corresponding functions.
|
|
- Remove connector init functions.
|
|
- Add <linux/hdmi.h> in phy_hdmi.h to reuse 'enum hdmi_colorspace'.
|
|
|
|
HDMI/DP PHY:
|
|
- Lowercase hex values
|
|
- Fix parameters indent issue on some functions
|
|
- Replace 'udelay' with 'usleep_range'
|
|
|
|
v5->v6:
|
|
HDMI/DP bridge driver
|
|
- 8501 is the part number of Cadence MHDP on i.MX8MQ.
|
|
Use MHDP8501 to name hdmi/dp drivers and files.
|
|
- Add compatible "fsl,imx8mq-mhdp8501-dp" for i.MX8MQ DP driver
|
|
- Add compatible "fsl,imx8mq-mhdp8501-hdmi" for i.MX8MQ HDMI driver
|
|
- Combine HDMI and DP dt-bindings into one file cdns,mhdp8501.yaml
|
|
- Fix HDMI scrambling is not enable issue when driver working in 4Kp60
|
|
mode.
|
|
- Add HDMI/DP PHY API mailbox protect.
|
|
|
|
HDMI/DP PHY driver:
|
|
- Rename DP and HDMI PHY files and move to folder phy/freescale/
|
|
- Remove properties num_lanes and link_rate from DP PHY driver.
|
|
- Combine HDMI and DP dt-bindings into one file fsl,imx8mq-dp-hdmi-phy.yaml
|
|
- Update compatible string to "fsl,imx8mq-dp-phy".
|
|
- Update compatible string to "fsl,imx8mq-hdmi-phy".
|
|
|
|
v4->v5:
|
|
- Drop "clk" suffix in clock name.
|
|
- Add output port property in the example of hdmi/dp.
|
|
|
|
v3->v4:
|
|
dt-bindings:
|
|
- Correct dt-bindings coding style and address review comments.
|
|
- Add apb_clk description.
|
|
- Add output port for HDMI/DP connector
|
|
PHY:
|
|
- Alphabetically sorted in Kconfig and Makefile for DP and HDMI PHY
|
|
- Remove unused registers define from HDMI and DP PHY drivers.
|
|
- More description in phy_hdmi.h.
|
|
- Add apb_clk to HDMI and DP phy driver.
|
|
HDMI/DP:
|
|
- Use get_unaligned_le32() to replace hardcode type conversion
|
|
in HDMI AVI infoframe data fill function.
|
|
- Add mailbox mutex lock in HDMI/DP driver for phy functions
|
|
to reslove race conditions between HDMI/DP and PHY drivers.
|
|
- Add apb_clk to both HDMI and DP driver.
|
|
- Rename some function names and add prefix with "cdns_hdmi/cdns_dp".
|
|
- Remove bpc 12 and 16 optional that not supported.
|
|
|
|
v2->v3:
|
|
Address comments for dt-bindings files.
|
|
- Correct dts-bindings file names
|
|
Rename phy-cadence-hdptx-dp.yaml to cdns,mhdp-imx8mq-dp.yaml
|
|
Rename phy-cadence-hdptx-hdmi.yaml to cdns,mhdp-imx8mq-hdmi.yaml
|
|
- Drop redundant words and descriptions.
|
|
- Correct hdmi/dp node name.
|
|
|
|
v2 is a completely different version compared to v1.
|
|
Previous v1 can be available here [1].
|
|
|
|
v1->v2:
|
|
- Reuse Cadence mailbox access functions from mhdp8546 instead of
|
|
rockchip DP.
|
|
- Mailbox access functions be convert to marco functions
|
|
that will be referenced by HDP-TX PHY(HDMI/DP) driver too.
|
|
- Plain bridge instead of component driver.
|
|
- Standalone Cadence HDP-TX PHY(HDMI/DP) driver.
|
|
- Audio driver are removed from the patch set, it will be add in another
|
|
patch set later.
|
|
|
|
[1] https://patchwork.kernel.org/project/linux-rockchip/cover/cover.1590982881.git.Sandor.yu@nxp.com/
|
|
|
|
Alexander Stein (2):
|
|
arm64: dts: imx8mq: Add DCSS + HDMI/DP display pipeline
|
|
arm64: dts: imx8mq: tqma8mq-mba8mx: Enable HDMI support
|
|
|
|
Sandor Yu (7):
|
|
soc: cadence: Create helper functions for Cadence MHDP
|
|
drm: bridge: cadence: Update mhdp8546 mailbox access functions
|
|
phy: Add HDMI configuration options
|
|
dt-bindings: display: bridge: Add Cadence MHDP8501
|
|
drm: bridge: Cadence: Add MHDP8501 DP/HDMI driver
|
|
dt-bindings: phy: Add Freescale iMX8MQ DP and HDMI PHY
|
|
phy: freescale: Add DisplayPort/HDMI Combo-PHY driver for i.MX8MQ
|
|
|
|
.../display/bridge/cdns,mhdp8501.yaml | 121 ++
|
|
.../bindings/phy/fsl,imx8mq-dp-hdmi-phy.yaml | 51 +
|
|
.../dts/freescale/imx8mq-tqma8mq-mba8mx.dts | 26 +
|
|
arch/arm64/boot/dts/freescale/imx8mq.dtsi | 68 +
|
|
arch/arm64/boot/dts/freescale/mba8mx.dtsi | 11 +
|
|
drivers/gpu/drm/bridge/cadence/Kconfig | 17 +
|
|
drivers/gpu/drm/bridge/cadence/Makefile | 2 +
|
|
.../drm/bridge/cadence/cdns-mhdp8501-core.c | 379 +++++
|
|
.../drm/bridge/cadence/cdns-mhdp8501-core.h | 380 +++++
|
|
.../gpu/drm/bridge/cadence/cdns-mhdp8501-dp.c | 694 ++++++++++
|
|
.../drm/bridge/cadence/cdns-mhdp8501-hdmi.c | 745 ++++++++++
|
|
.../drm/bridge/cadence/cdns-mhdp8546-core.c | 487 ++-----
|
|
.../drm/bridge/cadence/cdns-mhdp8546-core.h | 47 +-
|
|
.../drm/bridge/cadence/cdns-mhdp8546-hdcp.c | 212 +--
|
|
.../drm/bridge/cadence/cdns-mhdp8546-hdcp.h | 18 +-
|
|
drivers/phy/freescale/Kconfig | 10 +
|
|
drivers/phy/freescale/Makefile | 1 +
|
|
drivers/phy/freescale/phy-fsl-imx8mq-hdptx.c | 1231 +++++++++++++++++
|
|
drivers/soc/Kconfig | 1 +
|
|
drivers/soc/Makefile | 1 +
|
|
drivers/soc/cadence/Kconfig | 9 +
|
|
drivers/soc/cadence/Makefile | 3 +
|
|
drivers/soc/cadence/cdns-mhdp-helper.c | 565 ++++++++
|
|
include/linux/phy/phy-hdmi.h | 19 +
|
|
include/linux/phy/phy.h | 7 +-
|
|
include/soc/cadence/cdns-mhdp-helper.h | 129 ++
|
|
26 files changed, 4572 insertions(+), 662 deletions(-)
|
|
create mode 100644 Documentation/devicetree/bindings/display/bridge/cdns,mhdp8501.yaml
|
|
create mode 100644 Documentation/devicetree/bindings/phy/fsl,imx8mq-dp-hdmi-phy.yaml
|
|
create mode 100644 drivers/gpu/drm/bridge/cadence/cdns-mhdp8501-core.c
|
|
create mode 100644 drivers/gpu/drm/bridge/cadence/cdns-mhdp8501-core.h
|
|
create mode 100644 drivers/gpu/drm/bridge/cadence/cdns-mhdp8501-dp.c
|
|
create mode 100644 drivers/gpu/drm/bridge/cadence/cdns-mhdp8501-hdmi.c
|
|
create mode 100644 drivers/phy/freescale/phy-fsl-imx8mq-hdptx.c
|
|
create mode 100644 drivers/soc/cadence/Kconfig
|
|
create mode 100644 drivers/soc/cadence/Makefile
|
|
create mode 100644 drivers/soc/cadence/cdns-mhdp-helper.c
|
|
create mode 100644 include/linux/phy/phy-hdmi.h
|
|
create mode 100644 include/soc/cadence/cdns-mhdp-helper.h
|
|
|
|
--
|
|
2.34.1
|
|
|
|
|
|
|
|
From patchwork Tue Dec 17 06:51:43 2024
|
|
Content-Type: text/plain; charset="utf-8"
|
|
MIME-Version: 1.0
|
|
Content-Transfer-Encoding: 7bit
|
|
Subject: [v20,1/9] soc: cadence: Create helper functions for Cadence MHDP
|
|
From: Sandor Yu <Sandor.yu@nxp.com>
|
|
X-Patchwork-Id: 629288
|
|
Message-Id:
|
|
<7fd5d54e2594aadd66598888ddf512f3d6d30e5d.1734340233.git.Sandor.yu@nxp.com>
|
|
To: dmitry.baryshkov@linaro.org, andrzej.hajda@intel.com,
|
|
neil.armstrong@linaro.org, Laurent.pinchart@ideasonboard.com,
|
|
jonas@kwiboo.se, jernej.skrabec@gmail.com, airlied@gmail.com,
|
|
daniel@ffwll.ch, robh+dt@kernel.org, krzysztof.kozlowski+dt@linaro.org,
|
|
shawnguo@kernel.org, s.hauer@pengutronix.de, festevam@gmail.com,
|
|
vkoul@kernel.org, dri-devel@lists.freedesktop.org,
|
|
devicetree@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
|
|
linux-kernel@vger.kernel.org, linux-phy@lists.infradead.org,
|
|
mripard@kernel.org
|
|
Cc: kernel@pengutronix.de, linux-imx@nxp.com, Sandor.yu@nxp.com,
|
|
oliver.brown@nxp.com, alexander.stein@ew.tq-group.com, sam@ravnborg.org
|
|
Date: Tue, 17 Dec 2024 14:51:43 +0800
|
|
|
|
Cadence MHDP IP includes a firmware. Driver and firmware communicate
|
|
through a mailbox. The basic mailbox access functions in this patch
|
|
are derived from the DRM bridge MHDP8546 driver.
|
|
New mailbox access functions have been created based on different mailbox
|
|
return values and security types, making them reusable across different
|
|
MHDP driver versions and SOCs.
|
|
|
|
These helper fucntions will be reused in both the DRM bridge driver MDHP8501
|
|
and the i.MX8MQ HDPTX PHY driver.
|
|
|
|
Six mailbox access helper functions are introduced.
|
|
Three for non-secure mailbox access:
|
|
- cdns_mhdp_mailbox_send()
|
|
- cdns_mhdp_mailbox_send_recv()
|
|
- cdns_mhdp_mailbox_send_recv_multi()
|
|
The other three for secure mailbox access:
|
|
- cdns_mhdp_secure_mailbox_send()
|
|
- cdns_mhdp_secure_mailbox_send_recv()
|
|
- cdns_mhdp_secure_mailbox_send_recv_multi()
|
|
|
|
All MHDP commands that need to be passed through the mailbox
|
|
should be rewritten using these new helper functions.
|
|
|
|
The register read/write and DP DPCD read/write command functions
|
|
are also included in this new helper driver.
|
|
|
|
Signed-off-by: Sandor Yu <Sandor.yu@nxp.com>
|
|
---
|
|
v19->v20:
|
|
- new patch in v20.
|
|
The patch split from Patch #1 in v19 and move to a new folder drivers/soc/cadence
|
|
|
|
drivers/soc/Kconfig | 1 +
|
|
drivers/soc/Makefile | 1 +
|
|
drivers/soc/cadence/Kconfig | 9 +
|
|
drivers/soc/cadence/Makefile | 3 +
|
|
drivers/soc/cadence/cdns-mhdp-helper.c | 565 +++++++++++++++++++++++++
|
|
include/soc/cadence/cdns-mhdp-helper.h | 129 ++++++
|
|
6 files changed, 708 insertions(+)
|
|
create mode 100644 drivers/soc/cadence/Kconfig
|
|
create mode 100644 drivers/soc/cadence/Makefile
|
|
create mode 100644 drivers/soc/cadence/cdns-mhdp-helper.c
|
|
create mode 100644 include/soc/cadence/cdns-mhdp-helper.h
|
|
|
|
diff --git a/drivers/soc/Kconfig b/drivers/soc/Kconfig
|
|
index 6a8daeb8c4b96..f6c18114b2d68 100644
|
|
--- a/drivers/soc/Kconfig
|
|
+++ b/drivers/soc/Kconfig
|
|
@@ -6,6 +6,7 @@ source "drivers/soc/apple/Kconfig"
|
|
source "drivers/soc/aspeed/Kconfig"
|
|
source "drivers/soc/atmel/Kconfig"
|
|
source "drivers/soc/bcm/Kconfig"
|
|
+source "drivers/soc/cadence/Kconfig"
|
|
source "drivers/soc/canaan/Kconfig"
|
|
source "drivers/soc/cirrus/Kconfig"
|
|
source "drivers/soc/fsl/Kconfig"
|
|
diff --git a/drivers/soc/Makefile b/drivers/soc/Makefile
|
|
index 2037a8695cb28..a5fa4f4d15321 100644
|
|
--- a/drivers/soc/Makefile
|
|
+++ b/drivers/soc/Makefile
|
|
@@ -7,6 +7,7 @@ obj-y += apple/
|
|
obj-y += aspeed/
|
|
obj-$(CONFIG_ARCH_AT91) += atmel/
|
|
obj-y += bcm/
|
|
+obj-y += cadence/
|
|
obj-$(CONFIG_ARCH_CANAAN) += canaan/
|
|
obj-$(CONFIG_EP93XX_SOC) += cirrus/
|
|
obj-$(CONFIG_ARCH_DOVE) += dove/
|
|
diff --git a/drivers/soc/cadence/Kconfig b/drivers/soc/cadence/Kconfig
|
|
new file mode 100644
|
|
index 0000000000000..b668790660fa5
|
|
--- /dev/null
|
|
+++ b/drivers/soc/cadence/Kconfig
|
|
@@ -0,0 +1,9 @@
|
|
+# SPDX-License-Identifier: GPL-2.0
|
|
+
|
|
+config CDNS_MHDP_HELPER
|
|
+ tristate "Cadence MHDP Helper driver"
|
|
+ help
|
|
+ Enable Cadence MHDP helpers for mailbox, HDMI and DP.
|
|
+ This driver provides a foundational layer of mailbox communication for
|
|
+ various Cadence MHDP IP implementations, such as HDMI and DisplayPort.
|
|
+
|
|
diff --git a/drivers/soc/cadence/Makefile b/drivers/soc/cadence/Makefile
|
|
new file mode 100644
|
|
index 0000000000000..a1f42e1936ca5
|
|
--- /dev/null
|
|
+++ b/drivers/soc/cadence/Makefile
|
|
@@ -0,0 +1,3 @@
|
|
+# SPDX-License-Identifier: GPL-2.0-only
|
|
+
|
|
+obj-$(CONFIG_CDNS_MHDP_HELPER) += cdns-mhdp-helper.o
|
|
diff --git a/drivers/soc/cadence/cdns-mhdp-helper.c b/drivers/soc/cadence/cdns-mhdp-helper.c
|
|
new file mode 100644
|
|
index 0000000000000..f74b4cae134a2
|
|
--- /dev/null
|
|
+++ b/drivers/soc/cadence/cdns-mhdp-helper.c
|
|
@@ -0,0 +1,565 @@
|
|
+// SPDX-License-Identifier: GPL-2.0-only
|
|
+/*
|
|
+ * Copyright (C) 2023, 2024 NXP Semiconductor, Inc.
|
|
+ *
|
|
+ */
|
|
+#include <linux/dev_printk.h>
|
|
+#include <linux/module.h>
|
|
+#include <soc/cadence/cdns-mhdp-helper.h>
|
|
+
|
|
+/* Protects mailbox communications with the firmware */
|
|
+static DEFINE_MUTEX(mhdp_mailbox_mutex);
|
|
+
|
|
+/* Mailbox helper functions */
|
|
+static int mhdp_mailbox_read(void __iomem *regs)
|
|
+{
|
|
+ int ret, empty;
|
|
+
|
|
+ WARN_ON(!mutex_is_locked(&mhdp_mailbox_mutex));
|
|
+
|
|
+ ret = readx_poll_timeout(readl, regs + CDNS_MAILBOX_EMPTY,
|
|
+ empty, !empty, MAILBOX_RETRY_US,
|
|
+ MAILBOX_TIMEOUT_US);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ return readl(regs + CDNS_MAILBOX_RX_DATA) & 0xff;
|
|
+}
|
|
+
|
|
+static int mhdp_mailbox_write(void __iomem *regs, u8 val)
|
|
+{
|
|
+ int ret, full;
|
|
+
|
|
+ WARN_ON(!mutex_is_locked(&mhdp_mailbox_mutex));
|
|
+
|
|
+ ret = readx_poll_timeout(readl, regs + CDNS_MAILBOX_FULL,
|
|
+ full, !full, MAILBOX_RETRY_US,
|
|
+ MAILBOX_TIMEOUT_US);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ writel(val, regs + CDNS_MAILBOX_TX_DATA);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int mhdp_mailbox_recv_header(void __iomem *regs,
|
|
+ u8 module_id, u8 opcode,
|
|
+ u16 req_size)
|
|
+{
|
|
+ u32 mbox_size, i;
|
|
+ u8 header[4];
|
|
+ int ret;
|
|
+
|
|
+ /* read the header of the message */
|
|
+ for (i = 0; i < sizeof(header); i++) {
|
|
+ ret = mhdp_mailbox_read(regs);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ header[i] = ret;
|
|
+ }
|
|
+
|
|
+ mbox_size = get_unaligned_be16(header + 2);
|
|
+
|
|
+ /*
|
|
+ * If the message in mailbox is not what we want, we need to
|
|
+ * clear the mailbox by reading its contents.
|
|
+ * Response data length for HDCP TX HDCP_TRAN_IS_REC_ID_VALID depend on case.
|
|
+ */
|
|
+ if (opcode != header[0] ||
|
|
+ module_id != header[1] ||
|
|
+ (opcode != HDCP_TRAN_IS_REC_ID_VALID && req_size != mbox_size)) {
|
|
+ for (i = 0; i < mbox_size; i++)
|
|
+ if (mhdp_mailbox_read(regs) < 0)
|
|
+ break;
|
|
+
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int mhdp_mailbox_recv_data(void __iomem *regs,
|
|
+ u8 *buff, u16 buff_size)
|
|
+{
|
|
+ u32 i;
|
|
+ int ret;
|
|
+
|
|
+ for (i = 0; i < buff_size; i++) {
|
|
+ ret = mhdp_mailbox_read(regs);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ buff[i] = ret;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int mhdp_mailbox_send(void __iomem *regs, u8 module_id,
|
|
+ u8 opcode, u16 size, u8 *message)
|
|
+{
|
|
+ u8 header[4];
|
|
+ int ret, i;
|
|
+
|
|
+ header[0] = opcode;
|
|
+ header[1] = module_id;
|
|
+ put_unaligned_be16(size, header + 2);
|
|
+
|
|
+ for (i = 0; i < sizeof(header); i++) {
|
|
+ ret = mhdp_mailbox_write(regs, header[i]);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ for (i = 0; i < size; i++) {
|
|
+ ret = mhdp_mailbox_write(regs, message[i]);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * cdns_mhdp_mailbox_send - Sends a message via the MHDP mailbox.
|
|
+ *
|
|
+ * This function sends a message via the MHDP mailbox.
|
|
+ *
|
|
+ * @base: Pointer to the CDNS MHDP base structure.
|
|
+ * @module_id: ID of the module to send the message to.
|
|
+ * @opcode: Operation code of the message.
|
|
+ * @size: Size of the message data.
|
|
+ * @message: Pointer to the message data.
|
|
+ *
|
|
+ * Returns: 0 on success, negative error code on failure.
|
|
+ */
|
|
+int cdns_mhdp_mailbox_send(struct cdns_mhdp_base *base, u8 module_id,
|
|
+ u8 opcode, u16 size, u8 *message)
|
|
+{
|
|
+ guard(mutex)(&mhdp_mailbox_mutex);
|
|
+
|
|
+ return mhdp_mailbox_send(base->regs, module_id, opcode, size, message);
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(cdns_mhdp_mailbox_send);
|
|
+
|
|
+/**
|
|
+ * cdns_mhdp_mailbox_send_recv - Sends a message and receives a response.
|
|
+ *
|
|
+ * This function sends a message via the mailbox and then receives a response.
|
|
+ *
|
|
+ * @base: Pointer to the CDNS MHDP base structure.
|
|
+ * @module_id: ID of the module to send the message to.
|
|
+ * @opcode: Operation code of the message.
|
|
+ * @msg_size: Size of the message data.
|
|
+ * @msg: Pointer to the message data.
|
|
+ * @resp_size: Size of the response buffer.
|
|
+ * @resp: Pointer to the response buffer.
|
|
+ *
|
|
+ * Returns: 0 on success, negative error code on failure.
|
|
+ */
|
|
+int cdns_mhdp_mailbox_send_recv(struct cdns_mhdp_base *base,
|
|
+ u8 module_id, u8 opcode,
|
|
+ u16 msg_size, u8 *msg,
|
|
+ u16 resp_size, u8 *resp)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ guard(mutex)(&mhdp_mailbox_mutex);
|
|
+
|
|
+ ret = mhdp_mailbox_send(base->regs, module_id,
|
|
+ opcode, msg_size, msg);
|
|
+ if (ret) {
|
|
+ dev_err(base->dev, "ModuleID=%d, CMD=%d send failed: %d\n",
|
|
+ module_id, opcode, ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = mhdp_mailbox_recv_header(base->regs, module_id,
|
|
+ opcode, resp_size);
|
|
+ if (ret) {
|
|
+ dev_err(base->dev, "ModuleID=%d, CMD=%d recv header failed: %d\n",
|
|
+ module_id, opcode, ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = mhdp_mailbox_recv_data(base->regs, resp, resp_size);
|
|
+ if (ret)
|
|
+ dev_err(base->dev, "ModuleID=%d, CMD=%d recv data failed: %d\n",
|
|
+ module_id, opcode, ret);
|
|
+ return ret;
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(cdns_mhdp_mailbox_send_recv);
|
|
+
|
|
+/**
|
|
+ * cdns_mhdp_mailbox_send_recv_multi - Sends a message and receives multiple responses.
|
|
+ *
|
|
+ * This function sends a message to a specified module via the MHDP mailbox and
|
|
+ * then receives multiple responses from the module.
|
|
+ *
|
|
+ * @param base: Pointer to the CDNS MHDP base structure.
|
|
+ * @param module_id: ID of the module to send the message to.
|
|
+ * @param opcode: Operation code of the message.
|
|
+ * @param msg_size: Size of the message data.
|
|
+ * @param msg: Pointer to the message data.
|
|
+ * @param opcode_resp: Operation code of the response.
|
|
+ * @param resp1_size: Size of the first response buffer.
|
|
+ * @param resp1: Pointer to the first response buffer.
|
|
+ * @param resp2_size: Size of the second response buffer.
|
|
+ * @param resp2: Pointer to the second response buffer.
|
|
+ *
|
|
+ * Returns: 0 on success, negative error code on failure.
|
|
+ */
|
|
+int cdns_mhdp_mailbox_send_recv_multi(struct cdns_mhdp_base *base,
|
|
+ u8 module_id, u8 opcode,
|
|
+ u16 msg_size, u8 *msg,
|
|
+ u8 opcode_resp,
|
|
+ u16 resp1_size, u8 *resp1,
|
|
+ u16 resp2_size, u8 *resp2)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ guard(mutex)(&mhdp_mailbox_mutex);
|
|
+
|
|
+ ret = mhdp_mailbox_send(base->regs, module_id,
|
|
+ opcode, msg_size, msg);
|
|
+ if (ret) {
|
|
+ dev_err(base->dev, "ModuleID=%d, CMD=%d send failed: %d\n",
|
|
+ module_id, opcode, ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = mhdp_mailbox_recv_header(base->regs, module_id, opcode_resp,
|
|
+ resp1_size + resp2_size);
|
|
+ if (ret) {
|
|
+ dev_err(base->dev, "ModuleID=%d, Resp_CMD=%d recv header failed: %d\n",
|
|
+ module_id, opcode_resp, ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = mhdp_mailbox_recv_data(base->regs, resp1, resp1_size);
|
|
+ if (ret) {
|
|
+ dev_err(base->dev, "ModuleID=%d, Resp_CMD=%d recv data1 failed: %d\n",
|
|
+ module_id, opcode_resp, ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = mhdp_mailbox_recv_data(base->regs, resp2, resp2_size);
|
|
+ if (ret)
|
|
+ dev_err(base->dev, "ModuleID=%d, Resp_CMD=%d recv data1 failed: %d\n",
|
|
+ module_id, opcode_resp, ret);
|
|
+ return ret;
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(cdns_mhdp_mailbox_send_recv_multi);
|
|
+
|
|
+/**
|
|
+ * cdns_mhdp_secure_mailbox_send - Sends a secure message via the mailbox.
|
|
+ *
|
|
+ * This function sends a secure message to a specified module via the MHDP mailbox.
|
|
+ *
|
|
+ * @param base: Pointer to the CDNS MHDP base structure.
|
|
+ * @param module_id: ID of the module to send the message to.
|
|
+ * @param opcode: Operation code of the message.
|
|
+ * @param size: Size of the message data.
|
|
+ * @param message: Pointer to the message data.
|
|
+ *
|
|
+ * Returns: 0 on success, negative error code on failure.
|
|
+ */
|
|
+int cdns_mhdp_secure_mailbox_send(struct cdns_mhdp_base *base, u8 module_id,
|
|
+ u8 opcode, u16 size, u8 *message)
|
|
+{
|
|
+ guard(mutex)(&mhdp_mailbox_mutex);
|
|
+
|
|
+ return mhdp_mailbox_send(base->sapb_regs, module_id, opcode, size, message);
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(cdns_mhdp_secure_mailbox_send);
|
|
+
|
|
+/**
|
|
+ * cdns_mhdp_secure_mailbox_send_recv - Sends a secure message and receives a response.
|
|
+ *
|
|
+ * This function sends a secure message to a specified module via the mailbox and
|
|
+ * then receives a response from the module.
|
|
+ *
|
|
+ * @param base: Pointer to the CDNS MHDP base structure.
|
|
+ * @param module_id: ID of the module to send the message to.
|
|
+ * @param opcode: Operation code of the message.
|
|
+ * @param msg_size: Size of the message data.
|
|
+ * @param msg: Pointer to the message data.
|
|
+ * @param resp_size: Size of the response buffer.
|
|
+ * @param resp: Pointer to the response buffer.
|
|
+ *
|
|
+ * Returns: 0 on success, negative error code on failure.
|
|
+ */
|
|
+int cdns_mhdp_secure_mailbox_send_recv(struct cdns_mhdp_base *base,
|
|
+ u8 module_id, u8 opcode,
|
|
+ u16 msg_size, u8 *msg,
|
|
+ u16 resp_size, u8 *resp)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ guard(mutex)(&mhdp_mailbox_mutex);
|
|
+
|
|
+ ret = mhdp_mailbox_send(base->sapb_regs, module_id,
|
|
+ opcode, msg_size, msg);
|
|
+ if (ret) {
|
|
+ dev_err(base->dev, "ModuleID=%d, CMD=%d send failed: %d\n",
|
|
+ module_id, opcode, ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = mhdp_mailbox_recv_header(base->sapb_regs, module_id,
|
|
+ opcode, resp_size);
|
|
+ if (ret) {
|
|
+ dev_err(base->dev, "ModuleID=%d, CMD=%d recv header failed: %d\n",
|
|
+ module_id, opcode, ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = mhdp_mailbox_recv_data(base->sapb_regs, resp, resp_size);
|
|
+ if (ret)
|
|
+ dev_err(base->dev, "ModuleID=%d, CMD=%d recv data failed: %d\n",
|
|
+ module_id, opcode, ret);
|
|
+ return ret;
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(cdns_mhdp_secure_mailbox_send_recv);
|
|
+
|
|
+/**
|
|
+ * cdns_mhdp_secure_mailbox_send_recv_multi - Sends a secure message and receives multiple responses.
|
|
+ *
|
|
+ * This function sends a secure message to a specified module and receives multiple responses.
|
|
+ *
|
|
+ * @param base: Pointer to the CDNS MHDP base structure.
|
|
+ * @param module_id: ID of the module to send the message to.
|
|
+ * @param opcode: Operation code of the message.
|
|
+ * @param msg_size: Size of the message data.
|
|
+ * @param msg: Pointer to the message data.
|
|
+ * @param opcode_resp: Operation code of the response.
|
|
+ * @param resp1_size: Size of the first response buffer.
|
|
+ * @param resp1: Pointer to the first response buffer.
|
|
+ * @param resp2_size: Size of the second response buffer.
|
|
+ * @param resp2: Pointer to the second response buffer.
|
|
+ *
|
|
+ * Returns: 0 on success, negative error code on failure.
|
|
+ */
|
|
+int cdns_mhdp_secure_mailbox_send_recv_multi(struct cdns_mhdp_base *base,
|
|
+ u8 module_id, u8 opcode,
|
|
+ u16 msg_size, u8 *msg,
|
|
+ u8 opcode_resp,
|
|
+ u16 resp1_size, u8 *resp1,
|
|
+ u16 resp2_size, u8 *resp2)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ guard(mutex)(&mhdp_mailbox_mutex);
|
|
+
|
|
+ ret = mhdp_mailbox_send(base->sapb_regs, module_id,
|
|
+ opcode, msg_size, msg);
|
|
+ if (ret) {
|
|
+ dev_err(base->dev, "ModuleID=%d, CMD=%d send failed: %d\n",
|
|
+ module_id, opcode, ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = mhdp_mailbox_recv_header(base->sapb_regs, module_id,
|
|
+ opcode_resp,
|
|
+ resp1_size + resp2_size);
|
|
+ if (ret) {
|
|
+ dev_err(base->dev, "ModuleID=%d, Resp_CMD=%d recv header failed: %d\n",
|
|
+ module_id, opcode_resp, ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = mhdp_mailbox_recv_data(base->sapb_regs, resp1, resp1_size);
|
|
+ if (ret) {
|
|
+ dev_err(base->dev, "ModuleID=%d, Resp_CMD=%d recv data1 failed: %d\n",
|
|
+ module_id, opcode_resp, ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Response data length for HDCP TX HDCP_TRAN_IS_REC_ID_VALID depend on
|
|
+ * the number of HDCP receivers in resp1[0].
|
|
+ * 1 for regular case, more can be in repeater.
|
|
+ */
|
|
+ if (module_id == MB_MODULE_ID_HDCP_TX &&
|
|
+ opcode == HDCP_TRAN_IS_REC_ID_VALID)
|
|
+ ret = mhdp_mailbox_recv_data(base->sapb_regs, resp2, 5 * resp1[0]);
|
|
+ else
|
|
+ ret = mhdp_mailbox_recv_data(base->sapb_regs, resp2, resp2_size);
|
|
+ if (ret)
|
|
+ dev_err(base->dev, "ModuleID=%d, Resp_CMD=%d recv data2 failed: %d\n",
|
|
+ module_id, opcode_resp, ret);
|
|
+ return ret;
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(cdns_mhdp_secure_mailbox_send_recv_multi);
|
|
+
|
|
+/**
|
|
+ * cdns_mhdp_reg_read - Reads a general register value.
|
|
+ *
|
|
+ * This function reads the value from a general register
|
|
+ * using the mailbox.
|
|
+ *
|
|
+ * @param base: Pointer to the CDNS MHDP base structure.
|
|
+ * @param addr: Address of the register to read.
|
|
+ * @param value: Pointer to store the read value.
|
|
+ *
|
|
+ * Returns: 0 on success, negative error code on failure.
|
|
+ */
|
|
+int cdns_mhdp_reg_read(struct cdns_mhdp_base *base, u32 addr, u32 *value)
|
|
+{
|
|
+ u8 msg[4], resp[8];
|
|
+ int ret;
|
|
+
|
|
+ put_unaligned_be32(addr, msg);
|
|
+
|
|
+ ret = cdns_mhdp_mailbox_send_recv(base, MB_MODULE_ID_GENERAL,
|
|
+ GENERAL_REGISTER_READ,
|
|
+ sizeof(msg), msg, sizeof(resp), resp);
|
|
+ if (ret)
|
|
+ goto out;
|
|
+
|
|
+ /* Returned address value should be the same as requested */
|
|
+ if (memcmp(msg, resp, sizeof(msg))) {
|
|
+ ret = -EINVAL;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ *value = get_unaligned_be32(resp + 4);
|
|
+out:
|
|
+ if (ret) {
|
|
+ dev_err(base->dev, "Failed to read register\n");
|
|
+ *value = 0;
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(cdns_mhdp_reg_read);
|
|
+
|
|
+/**
|
|
+ * cdns_mhdp_reg_write - Writes a value to a general register.
|
|
+ *
|
|
+ * This function writes a value to a general register using the mailbox.
|
|
+ *
|
|
+ * @param base: Pointer to the CDNS MHDP base structure.
|
|
+ * @param addr: Address of the register to write to.
|
|
+ * @param val: Value to write to the register.
|
|
+ *
|
|
+ * Returns: 0 on success, negative error code on failure.
|
|
+ */
|
|
+int cdns_mhdp_reg_write(struct cdns_mhdp_base *base, u32 addr, u32 val)
|
|
+{
|
|
+ u8 msg[8];
|
|
+
|
|
+ put_unaligned_be32(addr, msg);
|
|
+ put_unaligned_be32(val, msg + 4);
|
|
+
|
|
+ return cdns_mhdp_mailbox_send(base, MB_MODULE_ID_GENERAL,
|
|
+ GENERAL_REGISTER_WRITE,
|
|
+ sizeof(msg), msg);
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(cdns_mhdp_reg_write);
|
|
+
|
|
+/* DPTX helper functions */
|
|
+/**
|
|
+ * cdns_mhdp_dp_reg_write_bit - Writes a bit field to a DP register.
|
|
+ *
|
|
+ * This function writes a specific bit field within a DP register
|
|
+ * using the MHDP mailbox.
|
|
+ *
|
|
+ * @param base: Pointer to the CDNS MHDP base structure.
|
|
+ * @param addr: Address of the DP register.
|
|
+ * @param start_bit: Starting bit position within the register.
|
|
+ * @param bits_no: Number of bits to write.
|
|
+ * @param val: Value to write to the bit field.
|
|
+ *
|
|
+ * Returns: 0 on success, negative error code on failure.
|
|
+ */
|
|
+int cdns_mhdp_dp_reg_write_bit(struct cdns_mhdp_base *base, u16 addr,
|
|
+ u8 start_bit, u8 bits_no, u32 val)
|
|
+{
|
|
+ u8 field[8];
|
|
+
|
|
+ put_unaligned_be16(addr, field);
|
|
+ field[2] = start_bit;
|
|
+ field[3] = bits_no;
|
|
+ put_unaligned_be32(val, field + 4);
|
|
+
|
|
+ return cdns_mhdp_mailbox_send(base, MB_MODULE_ID_DP_TX,
|
|
+ DPTX_WRITE_FIELD, sizeof(field), field);
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(cdns_mhdp_dp_reg_write_bit);
|
|
+
|
|
+/**
|
|
+ * cdns_mhdp_dpcd_read - Reads data from a DPCD register.
|
|
+ *
|
|
+ * This function reads data from a specified DPCD register
|
|
+ * using the MHDP mailbox.
|
|
+ *
|
|
+ * @param base: Pointer to the CDNS MHDP base structure.
|
|
+ * @param addr: Address of the DPCD register to read.
|
|
+ * @param data: Buffer to store the read data.
|
|
+ * @param len: Length of the data to read.
|
|
+ *
|
|
+ * Returns: 0 on success, negative error code on failure.
|
|
+ */
|
|
+int cdns_mhdp_dpcd_read(struct cdns_mhdp_base *base,
|
|
+ u32 addr, u8 *data, u16 len)
|
|
+{
|
|
+ u8 msg[5], reg[5];
|
|
+
|
|
+ put_unaligned_be16(len, msg);
|
|
+ put_unaligned_be24(addr, msg + 2);
|
|
+
|
|
+ return cdns_mhdp_mailbox_send_recv_multi(base,
|
|
+ MB_MODULE_ID_DP_TX,
|
|
+ DPTX_READ_DPCD,
|
|
+ sizeof(msg), msg,
|
|
+ DPTX_READ_DPCD,
|
|
+ sizeof(reg), reg,
|
|
+ len, data);
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(cdns_mhdp_dpcd_read);
|
|
+
|
|
+/**
|
|
+ * cdns_mhdp_dpcd_write - Writes data to a DPCD register.
|
|
+ *
|
|
+ * This function writes data to a specified DPCD register
|
|
+ * using the MHDP mailbox.
|
|
+ *
|
|
+ * @param base: Pointer to the CDNS MHDP base structure.
|
|
+ * @param addr: Address of the DPCD register to write to.
|
|
+ * @param value: Value to write to the register.
|
|
+ *
|
|
+ * Returns: 0 on success, negative error code on failure.
|
|
+ */
|
|
+int cdns_mhdp_dpcd_write(struct cdns_mhdp_base *base, u32 addr, u8 value)
|
|
+{
|
|
+ u8 msg[6], reg[5];
|
|
+ int ret;
|
|
+
|
|
+ put_unaligned_be16(1, msg);
|
|
+ put_unaligned_be24(addr, msg + 2);
|
|
+ msg[5] = value;
|
|
+
|
|
+ ret = cdns_mhdp_mailbox_send_recv(base, MB_MODULE_ID_DP_TX,
|
|
+ DPTX_WRITE_DPCD,
|
|
+ sizeof(msg), msg, sizeof(reg), reg);
|
|
+ if (ret) {
|
|
+ dev_err(base->dev, "dpcd write failed: %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ if (addr != get_unaligned_be24(reg + 2)) {
|
|
+ dev_err(base->dev, "Invalid response: expected address 0x%06x, got 0x%06x\n",
|
|
+ addr, get_unaligned_be24(reg + 2));
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(cdns_mhdp_dpcd_write);
|
|
+
|
|
+MODULE_DESCRIPTION("Cadence MHDP Helper driver");
|
|
+MODULE_AUTHOR("Sandor Yu <Sandor.yu@nxp.com>");
|
|
+MODULE_LICENSE("GPL");
|
|
diff --git a/include/soc/cadence/cdns-mhdp-helper.h b/include/soc/cadence/cdns-mhdp-helper.h
|
|
new file mode 100644
|
|
index 0000000000000..25b9737de615f
|
|
--- /dev/null
|
|
+++ b/include/soc/cadence/cdns-mhdp-helper.h
|
|
@@ -0,0 +1,129 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0-only */
|
|
+/*
|
|
+ * Copyright (C) 2023-2024 NXP Semiconductor, Inc.
|
|
+ */
|
|
+#ifndef __CDNS_MHDP_HELPER_H__
|
|
+#define __CDNS_MHDP_HELPER_H__
|
|
+
|
|
+#include <linux/iopoll.h>
|
|
+#include <linux/unaligned.h>
|
|
+
|
|
+/* mailbox regs offset */
|
|
+#define CDNS_MAILBOX_FULL 0x00008
|
|
+#define CDNS_MAILBOX_EMPTY 0x0000c
|
|
+#define CDNS_MAILBOX_TX_DATA 0x00010
|
|
+#define CDNS_MAILBOX_RX_DATA 0x00014
|
|
+
|
|
+#define MAILBOX_RETRY_US 1000
|
|
+#define MAILBOX_TIMEOUT_US 2000000
|
|
+
|
|
+/* Module ID Code */
|
|
+#define MB_MODULE_ID_DP_TX 0x01
|
|
+#define MB_MODULE_ID_HDMI_TX 0x03
|
|
+#define MB_MODULE_ID_HDCP_TX 0x07
|
|
+#define MB_MODULE_ID_HDCP_RX 0x08
|
|
+#define MB_MODULE_ID_HDCP_GENERAL 0x09
|
|
+#define MB_MODULE_ID_GENERAL 0x0A
|
|
+
|
|
+/* General Commands */
|
|
+#define GENERAL_MAIN_CONTROL 0x01
|
|
+#define GENERAL_TEST_ECHO 0x02
|
|
+#define GENERAL_BUS_SETTINGS 0x03
|
|
+#define GENERAL_TEST_ACCESS 0x04
|
|
+#define GENERAL_REGISTER_WRITE 0x05
|
|
+#define GENERAL_WRITE_FIELD 0x06
|
|
+#define GENERAL_REGISTER_READ 0x07
|
|
+#define GENERAL_GET_HPD_STATE 0x11
|
|
+
|
|
+/* DPTX Commands */
|
|
+#define DPTX_SET_POWER_MNG 0x00
|
|
+#define DPTX_SET_HOST_CAPABILITIES 0x01
|
|
+#define DPTX_GET_EDID 0x02
|
|
+#define DPTX_READ_DPCD 0x03
|
|
+#define DPTX_WRITE_DPCD 0x04
|
|
+#define DPTX_ENABLE_EVENT 0x05
|
|
+#define DPTX_WRITE_REGISTER 0x06
|
|
+#define DPTX_READ_REGISTER 0x07
|
|
+#define DPTX_WRITE_FIELD 0x08
|
|
+#define DPTX_TRAINING_CONTROL 0x09
|
|
+#define DPTX_READ_EVENT 0x0a
|
|
+#define DPTX_READ_LINK_STAT 0x0b
|
|
+#define DPTX_SET_VIDEO 0x0c
|
|
+#define DPTX_SET_AUDIO 0x0d
|
|
+#define DPTX_GET_LAST_AUX_STAUS 0x0e
|
|
+#define DPTX_SET_LINK_BREAK_POINT 0x0f
|
|
+#define DPTX_FORCE_LANES 0x10
|
|
+#define DPTX_HPD_STATE 0x11
|
|
+#define DPTX_ADJUST_LT 0x12
|
|
+
|
|
+/* HDMI TX Commands */
|
|
+#define HDMI_TX_READ 0x00
|
|
+#define HDMI_TX_WRITE 0x01
|
|
+#define HDMI_TX_UPDATE_READ 0x02
|
|
+#define HDMI_TX_EDID 0x03
|
|
+#define HDMI_TX_EVENTS 0x04
|
|
+#define HDMI_TX_HPD_STATUS 0x05
|
|
+
|
|
+/* HDCP TX Commands */
|
|
+#define HDCP_TRAN_CONFIGURATION 0x00
|
|
+#define HDCP2X_TX_SET_PUBLIC_KEY_PARAMS 0x01
|
|
+#define HDCP2X_TX_SET_DEBUG_RANDOM_NUMBERS 0x02
|
|
+#define HDCP2X_TX_RESPOND_KM 0x03
|
|
+#define HDCP1_TX_SEND_KEYS 0x04
|
|
+#define HDCP1_TX_SEND_RANDOM_AN 0x05
|
|
+#define HDCP_TRAN_STATUS_CHANGE 0x06
|
|
+#define HDCP2X_TX_IS_KM_STORED 0x07
|
|
+#define HDCP2X_TX_STORE_KM 0x08
|
|
+#define HDCP_TRAN_IS_REC_ID_VALID 0x09
|
|
+#define HDCP_TRAN_RESPOND_RECEIVER_ID_VALID 0x09
|
|
+#define HDCP_TRAN_TEST_KEYS 0x0a
|
|
+#define HDCP2X_TX_SET_KM_KEY_PARAMS 0x0b
|
|
+#define HDCP_NUM_OF_SUPPORTED_MESSAGES 0x0c
|
|
+
|
|
+struct cdns_mhdp_base {
|
|
+ struct device *dev;
|
|
+ void __iomem *regs;
|
|
+ void __iomem *sapb_regs;
|
|
+};
|
|
+
|
|
+/* Mailbox helper functions */
|
|
+int cdns_mhdp_mailbox_send(struct cdns_mhdp_base *base,
|
|
+ u8 module_id, u8 opcode,
|
|
+ u16 size, u8 *message);
|
|
+int cdns_mhdp_mailbox_send_recv(struct cdns_mhdp_base *base,
|
|
+ u8 module_id, u8 opcode,
|
|
+ u16 msg_size, u8 *msg,
|
|
+ u16 resp_size, u8 *resp);
|
|
+int cdns_mhdp_mailbox_send_recv_multi(struct cdns_mhdp_base *base,
|
|
+ u8 module_id, u8 opcode,
|
|
+ u16 msg_size, u8 *msg,
|
|
+ u8 opcode_resp,
|
|
+ u16 resp1_size, u8 *resp1,
|
|
+ u16 resp2_size, u8 *resp2);
|
|
+
|
|
+/* Secure mailbox helper functions */
|
|
+int cdns_mhdp_secure_mailbox_send(struct cdns_mhdp_base *base,
|
|
+ u8 module_id, u8 opcode,
|
|
+ u16 size, u8 *message);
|
|
+int cdns_mhdp_secure_mailbox_send_recv(struct cdns_mhdp_base *base,
|
|
+ u8 module_id, u8 opcode,
|
|
+ u16 msg_size, u8 *msg,
|
|
+ u16 resp_size, u8 *resp);
|
|
+int cdns_mhdp_secure_mailbox_send_recv_multi(struct cdns_mhdp_base *base,
|
|
+ u8 module_id, u8 opcode,
|
|
+ u16 msg_size, u8 *msg,
|
|
+ u8 opcode_resp,
|
|
+ u16 resp1_size, u8 *resp1,
|
|
+ u16 resp2_size, u8 *resp2);
|
|
+
|
|
+/* General commands helper functions */
|
|
+int cdns_mhdp_reg_read(struct cdns_mhdp_base *base, u32 addr, u32 *value);
|
|
+int cdns_mhdp_reg_write(struct cdns_mhdp_base *base, u32 addr, u32 val);
|
|
+
|
|
+/* DPTX commands helper functions */
|
|
+int cdns_mhdp_dp_reg_write_bit(struct cdns_mhdp_base *base, u16 addr,
|
|
+ u8 start_bit, u8 bits_no, u32 val);
|
|
+int cdns_mhdp_dpcd_read(struct cdns_mhdp_base *base,
|
|
+ u32 addr, u8 *data, u16 len);
|
|
+int cdns_mhdp_dpcd_write(struct cdns_mhdp_base *base, u32 addr, u8 value);
|
|
+#endif /* __CDNS_MHDP_HELPER_H__ */
|
|
|
|
From patchwork Tue Dec 17 06:51:44 2024
|
|
Content-Type: text/plain; charset="utf-8"
|
|
MIME-Version: 1.0
|
|
Content-Transfer-Encoding: 7bit
|
|
Subject: [v20,2/9] drm: bridge: cadence: Update mhdp8546 mailbox access
|
|
functions
|
|
From: Sandor Yu <Sandor.yu@nxp.com>
|
|
X-Patchwork-Id: 629289
|
|
Message-Id:
|
|
<74bc3f2ff56348afd9d773589236ddf06dc3d45c.1734340233.git.Sandor.yu@nxp.com>
|
|
To: dmitry.baryshkov@linaro.org, andrzej.hajda@intel.com,
|
|
neil.armstrong@linaro.org, Laurent.pinchart@ideasonboard.com,
|
|
jonas@kwiboo.se, jernej.skrabec@gmail.com, airlied@gmail.com,
|
|
daniel@ffwll.ch, robh+dt@kernel.org, krzysztof.kozlowski+dt@linaro.org,
|
|
shawnguo@kernel.org, s.hauer@pengutronix.de, festevam@gmail.com,
|
|
vkoul@kernel.org, dri-devel@lists.freedesktop.org,
|
|
devicetree@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
|
|
linux-kernel@vger.kernel.org, linux-phy@lists.infradead.org,
|
|
mripard@kernel.org
|
|
Cc: kernel@pengutronix.de, linux-imx@nxp.com, Sandor.yu@nxp.com,
|
|
oliver.brown@nxp.com, alexander.stein@ew.tq-group.com, sam@ravnborg.org
|
|
Date: Tue, 17 Dec 2024 14:51:44 +0800
|
|
|
|
Basic mailbox access functions are removed, they are replaced by
|
|
mailbox helper functions:
|
|
- cdns_mhdp_mailbox_send()
|
|
- cdns_mhdp_mailbox_send_recv()
|
|
- cdns_mhdp_mailbox_send_recv_multi()
|
|
- cdns_mhdp_secure_mailbox_send()
|
|
- cdns_mhdp_secure_mailbox_send_recv()
|
|
- cdns_mhdp_secure_mailbox_send_recv_multi()
|
|
|
|
All MHDP commands that need to be passed through the mailbox
|
|
have been rewritten using these new helper functions.
|
|
|
|
Signed-off-by: Sandor Yu <Sandor.yu@nxp.com>
|
|
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
|
|
---
|
|
v19->v20:
|
|
- remove mhdp helper functions from the patch.
|
|
|
|
v18->v19:
|
|
- Use guard(mutex)
|
|
- Add kerneldocs for all new APIs.
|
|
- Detail comments for mailbox access specific case.
|
|
- Remove cdns_mhdp_dp_reg_write() because it is not needed by driver now.
|
|
|
|
v17->v18:
|
|
- Create three ordinary mailbox access APIs
|
|
cdns_mhdp_mailbox_send
|
|
cdns_mhdp_mailbox_send_recv
|
|
cdns_mhdp_mailbox_send_recv_multi
|
|
- Create three secure mailbox access APIs
|
|
cdns_mhdp_secure_mailbox_send
|
|
cdns_mhdp_secure_mailbox_send_recv
|
|
cdns_mhdp_secure_mailbox_send_recv_multi
|
|
- MHDP8546 DP and HDCP commands that need access mailbox are rewrited
|
|
with above 6 API functions.
|
|
|
|
v16->v17:
|
|
- Replaces the local mutex mbox_mutex with a global mutex mhdp_mailbox_mutex
|
|
|
|
v12->v16:
|
|
*No change.
|
|
|
|
drivers/gpu/drm/bridge/cadence/Kconfig | 1 +
|
|
.../drm/bridge/cadence/cdns-mhdp8546-core.c | 487 +++---------------
|
|
.../drm/bridge/cadence/cdns-mhdp8546-core.h | 47 +-
|
|
.../drm/bridge/cadence/cdns-mhdp8546-hdcp.c | 212 +-------
|
|
.../drm/bridge/cadence/cdns-mhdp8546-hdcp.h | 18 +-
|
|
5 files changed, 104 insertions(+), 661 deletions(-)
|
|
|
|
diff --git a/drivers/gpu/drm/bridge/cadence/Kconfig b/drivers/gpu/drm/bridge/cadence/Kconfig
|
|
index cced81633ddcd..dbb06533ccab2 100644
|
|
--- a/drivers/gpu/drm/bridge/cadence/Kconfig
|
|
+++ b/drivers/gpu/drm/bridge/cadence/Kconfig
|
|
@@ -28,6 +28,7 @@ config DRM_CDNS_MHDP8546
|
|
select DRM_DISPLAY_HELPER
|
|
select DRM_KMS_HELPER
|
|
select DRM_PANEL_BRIDGE
|
|
+ select CDNS_MHDP_HELPER
|
|
depends on OF
|
|
help
|
|
Support Cadence DPI to DP bridge. This is an internal
|
|
diff --git a/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.c b/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.c
|
|
index d081850e3c03e..bd897c3ae7642 100644
|
|
--- a/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.c
|
|
+++ b/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.c
|
|
@@ -73,302 +73,18 @@ static void cdns_mhdp_bridge_hpd_disable(struct drm_bridge *bridge)
|
|
mhdp->regs + CDNS_APB_INT_MASK);
|
|
}
|
|
|
|
-static int cdns_mhdp_mailbox_read(struct cdns_mhdp_device *mhdp)
|
|
-{
|
|
- int ret, empty;
|
|
-
|
|
- WARN_ON(!mutex_is_locked(&mhdp->mbox_mutex));
|
|
-
|
|
- ret = readx_poll_timeout(readl, mhdp->regs + CDNS_MAILBOX_EMPTY,
|
|
- empty, !empty, MAILBOX_RETRY_US,
|
|
- MAILBOX_TIMEOUT_US);
|
|
- if (ret < 0)
|
|
- return ret;
|
|
-
|
|
- return readl(mhdp->regs + CDNS_MAILBOX_RX_DATA) & 0xff;
|
|
-}
|
|
-
|
|
-static int cdns_mhdp_mailbox_write(struct cdns_mhdp_device *mhdp, u8 val)
|
|
-{
|
|
- int ret, full;
|
|
-
|
|
- WARN_ON(!mutex_is_locked(&mhdp->mbox_mutex));
|
|
-
|
|
- ret = readx_poll_timeout(readl, mhdp->regs + CDNS_MAILBOX_FULL,
|
|
- full, !full, MAILBOX_RETRY_US,
|
|
- MAILBOX_TIMEOUT_US);
|
|
- if (ret < 0)
|
|
- return ret;
|
|
-
|
|
- writel(val, mhdp->regs + CDNS_MAILBOX_TX_DATA);
|
|
-
|
|
- return 0;
|
|
-}
|
|
-
|
|
-static int cdns_mhdp_mailbox_recv_header(struct cdns_mhdp_device *mhdp,
|
|
- u8 module_id, u8 opcode,
|
|
- u16 req_size)
|
|
-{
|
|
- u32 mbox_size, i;
|
|
- u8 header[4];
|
|
- int ret;
|
|
-
|
|
- /* read the header of the message */
|
|
- for (i = 0; i < sizeof(header); i++) {
|
|
- ret = cdns_mhdp_mailbox_read(mhdp);
|
|
- if (ret < 0)
|
|
- return ret;
|
|
-
|
|
- header[i] = ret;
|
|
- }
|
|
-
|
|
- mbox_size = get_unaligned_be16(header + 2);
|
|
-
|
|
- if (opcode != header[0] || module_id != header[1] ||
|
|
- req_size != mbox_size) {
|
|
- /*
|
|
- * If the message in mailbox is not what we want, we need to
|
|
- * clear the mailbox by reading its contents.
|
|
- */
|
|
- for (i = 0; i < mbox_size; i++)
|
|
- if (cdns_mhdp_mailbox_read(mhdp) < 0)
|
|
- break;
|
|
-
|
|
- return -EINVAL;
|
|
- }
|
|
-
|
|
- return 0;
|
|
-}
|
|
-
|
|
-static int cdns_mhdp_mailbox_recv_data(struct cdns_mhdp_device *mhdp,
|
|
- u8 *buff, u16 buff_size)
|
|
-{
|
|
- u32 i;
|
|
- int ret;
|
|
-
|
|
- for (i = 0; i < buff_size; i++) {
|
|
- ret = cdns_mhdp_mailbox_read(mhdp);
|
|
- if (ret < 0)
|
|
- return ret;
|
|
-
|
|
- buff[i] = ret;
|
|
- }
|
|
-
|
|
- return 0;
|
|
-}
|
|
-
|
|
-static int cdns_mhdp_mailbox_send(struct cdns_mhdp_device *mhdp, u8 module_id,
|
|
- u8 opcode, u16 size, u8 *message)
|
|
-{
|
|
- u8 header[4];
|
|
- int ret, i;
|
|
-
|
|
- header[0] = opcode;
|
|
- header[1] = module_id;
|
|
- put_unaligned_be16(size, header + 2);
|
|
-
|
|
- for (i = 0; i < sizeof(header); i++) {
|
|
- ret = cdns_mhdp_mailbox_write(mhdp, header[i]);
|
|
- if (ret)
|
|
- return ret;
|
|
- }
|
|
-
|
|
- for (i = 0; i < size; i++) {
|
|
- ret = cdns_mhdp_mailbox_write(mhdp, message[i]);
|
|
- if (ret)
|
|
- return ret;
|
|
- }
|
|
-
|
|
- return 0;
|
|
-}
|
|
-
|
|
-static
|
|
-int cdns_mhdp_reg_read(struct cdns_mhdp_device *mhdp, u32 addr, u32 *value)
|
|
-{
|
|
- u8 msg[4], resp[8];
|
|
- int ret;
|
|
-
|
|
- put_unaligned_be32(addr, msg);
|
|
-
|
|
- mutex_lock(&mhdp->mbox_mutex);
|
|
-
|
|
- ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_GENERAL,
|
|
- GENERAL_REGISTER_READ,
|
|
- sizeof(msg), msg);
|
|
- if (ret)
|
|
- goto out;
|
|
-
|
|
- ret = cdns_mhdp_mailbox_recv_header(mhdp, MB_MODULE_ID_GENERAL,
|
|
- GENERAL_REGISTER_READ,
|
|
- sizeof(resp));
|
|
- if (ret)
|
|
- goto out;
|
|
-
|
|
- ret = cdns_mhdp_mailbox_recv_data(mhdp, resp, sizeof(resp));
|
|
- if (ret)
|
|
- goto out;
|
|
-
|
|
- /* Returned address value should be the same as requested */
|
|
- if (memcmp(msg, resp, sizeof(msg))) {
|
|
- ret = -EINVAL;
|
|
- goto out;
|
|
- }
|
|
-
|
|
- *value = get_unaligned_be32(resp + 4);
|
|
-
|
|
-out:
|
|
- mutex_unlock(&mhdp->mbox_mutex);
|
|
- if (ret) {
|
|
- dev_err(mhdp->dev, "Failed to read register\n");
|
|
- *value = 0;
|
|
- }
|
|
-
|
|
- return ret;
|
|
-}
|
|
-
|
|
-static
|
|
-int cdns_mhdp_reg_write(struct cdns_mhdp_device *mhdp, u16 addr, u32 val)
|
|
-{
|
|
- u8 msg[6];
|
|
- int ret;
|
|
-
|
|
- put_unaligned_be16(addr, msg);
|
|
- put_unaligned_be32(val, msg + 2);
|
|
-
|
|
- mutex_lock(&mhdp->mbox_mutex);
|
|
-
|
|
- ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX,
|
|
- DPTX_WRITE_REGISTER, sizeof(msg), msg);
|
|
-
|
|
- mutex_unlock(&mhdp->mbox_mutex);
|
|
-
|
|
- return ret;
|
|
-}
|
|
-
|
|
-static
|
|
-int cdns_mhdp_reg_write_bit(struct cdns_mhdp_device *mhdp, u16 addr,
|
|
- u8 start_bit, u8 bits_no, u32 val)
|
|
-{
|
|
- u8 field[8];
|
|
- int ret;
|
|
-
|
|
- put_unaligned_be16(addr, field);
|
|
- field[2] = start_bit;
|
|
- field[3] = bits_no;
|
|
- put_unaligned_be32(val, field + 4);
|
|
-
|
|
- mutex_lock(&mhdp->mbox_mutex);
|
|
-
|
|
- ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX,
|
|
- DPTX_WRITE_FIELD, sizeof(field), field);
|
|
-
|
|
- mutex_unlock(&mhdp->mbox_mutex);
|
|
-
|
|
- return ret;
|
|
-}
|
|
-
|
|
-static
|
|
-int cdns_mhdp_dpcd_read(struct cdns_mhdp_device *mhdp,
|
|
- u32 addr, u8 *data, u16 len)
|
|
-{
|
|
- u8 msg[5], reg[5];
|
|
- int ret;
|
|
-
|
|
- put_unaligned_be16(len, msg);
|
|
- put_unaligned_be24(addr, msg + 2);
|
|
-
|
|
- mutex_lock(&mhdp->mbox_mutex);
|
|
-
|
|
- ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX,
|
|
- DPTX_READ_DPCD, sizeof(msg), msg);
|
|
- if (ret)
|
|
- goto out;
|
|
-
|
|
- ret = cdns_mhdp_mailbox_recv_header(mhdp, MB_MODULE_ID_DP_TX,
|
|
- DPTX_READ_DPCD,
|
|
- sizeof(reg) + len);
|
|
- if (ret)
|
|
- goto out;
|
|
-
|
|
- ret = cdns_mhdp_mailbox_recv_data(mhdp, reg, sizeof(reg));
|
|
- if (ret)
|
|
- goto out;
|
|
-
|
|
- ret = cdns_mhdp_mailbox_recv_data(mhdp, data, len);
|
|
-
|
|
-out:
|
|
- mutex_unlock(&mhdp->mbox_mutex);
|
|
-
|
|
- return ret;
|
|
-}
|
|
-
|
|
-static
|
|
-int cdns_mhdp_dpcd_write(struct cdns_mhdp_device *mhdp, u32 addr, u8 value)
|
|
-{
|
|
- u8 msg[6], reg[5];
|
|
- int ret;
|
|
-
|
|
- put_unaligned_be16(1, msg);
|
|
- put_unaligned_be24(addr, msg + 2);
|
|
- msg[5] = value;
|
|
-
|
|
- mutex_lock(&mhdp->mbox_mutex);
|
|
-
|
|
- ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX,
|
|
- DPTX_WRITE_DPCD, sizeof(msg), msg);
|
|
- if (ret)
|
|
- goto out;
|
|
-
|
|
- ret = cdns_mhdp_mailbox_recv_header(mhdp, MB_MODULE_ID_DP_TX,
|
|
- DPTX_WRITE_DPCD, sizeof(reg));
|
|
- if (ret)
|
|
- goto out;
|
|
-
|
|
- ret = cdns_mhdp_mailbox_recv_data(mhdp, reg, sizeof(reg));
|
|
- if (ret)
|
|
- goto out;
|
|
-
|
|
- if (addr != get_unaligned_be24(reg + 2))
|
|
- ret = -EINVAL;
|
|
-
|
|
-out:
|
|
- mutex_unlock(&mhdp->mbox_mutex);
|
|
-
|
|
- if (ret)
|
|
- dev_err(mhdp->dev, "dpcd write failed: %d\n", ret);
|
|
- return ret;
|
|
-}
|
|
-
|
|
static
|
|
int cdns_mhdp_set_firmware_active(struct cdns_mhdp_device *mhdp, bool enable)
|
|
{
|
|
- u8 msg[5];
|
|
- int ret, i;
|
|
-
|
|
- msg[0] = GENERAL_MAIN_CONTROL;
|
|
- msg[1] = MB_MODULE_ID_GENERAL;
|
|
- msg[2] = 0;
|
|
- msg[3] = 1;
|
|
- msg[4] = enable ? FW_ACTIVE : FW_STANDBY;
|
|
-
|
|
- mutex_lock(&mhdp->mbox_mutex);
|
|
-
|
|
- for (i = 0; i < sizeof(msg); i++) {
|
|
- ret = cdns_mhdp_mailbox_write(mhdp, msg[i]);
|
|
- if (ret)
|
|
- goto out;
|
|
- }
|
|
-
|
|
- /* read the firmware state */
|
|
- ret = cdns_mhdp_mailbox_recv_data(mhdp, msg, sizeof(msg));
|
|
- if (ret)
|
|
- goto out;
|
|
-
|
|
- ret = 0;
|
|
+ u8 status;
|
|
+ int ret;
|
|
|
|
-out:
|
|
- mutex_unlock(&mhdp->mbox_mutex);
|
|
+ status = enable ? FW_ACTIVE : FW_STANDBY;
|
|
|
|
+ ret = cdns_mhdp_mailbox_send_recv(&mhdp->base, MB_MODULE_ID_GENERAL,
|
|
+ GENERAL_MAIN_CONTROL,
|
|
+ sizeof(status), &status,
|
|
+ sizeof(status), &status);
|
|
if (ret < 0)
|
|
dev_err(mhdp->dev, "set firmware active failed\n");
|
|
return ret;
|
|
@@ -380,34 +96,18 @@ int cdns_mhdp_get_hpd_status(struct cdns_mhdp_device *mhdp)
|
|
u8 status;
|
|
int ret;
|
|
|
|
- mutex_lock(&mhdp->mbox_mutex);
|
|
-
|
|
- ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX,
|
|
- DPTX_HPD_STATE, 0, NULL);
|
|
- if (ret)
|
|
- goto err_get_hpd;
|
|
-
|
|
- ret = cdns_mhdp_mailbox_recv_header(mhdp, MB_MODULE_ID_DP_TX,
|
|
- DPTX_HPD_STATE,
|
|
- sizeof(status));
|
|
- if (ret)
|
|
- goto err_get_hpd;
|
|
+ ret = cdns_mhdp_mailbox_send_recv(&mhdp->base, MB_MODULE_ID_DP_TX,
|
|
+ DPTX_HPD_STATE,
|
|
+ 0, NULL,
|
|
+ sizeof(status), &status);
|
|
|
|
- ret = cdns_mhdp_mailbox_recv_data(mhdp, &status, sizeof(status));
|
|
if (ret)
|
|
- goto err_get_hpd;
|
|
-
|
|
- mutex_unlock(&mhdp->mbox_mutex);
|
|
+ return ret;
|
|
|
|
dev_dbg(mhdp->dev, "%s: HPD %splugged\n", __func__,
|
|
status ? "" : "un");
|
|
|
|
return status;
|
|
-
|
|
-err_get_hpd:
|
|
- mutex_unlock(&mhdp->mbox_mutex);
|
|
-
|
|
- return ret;
|
|
}
|
|
|
|
static
|
|
@@ -418,28 +118,17 @@ int cdns_mhdp_get_edid_block(void *data, u8 *edid,
|
|
u8 msg[2], reg[2], i;
|
|
int ret;
|
|
|
|
- mutex_lock(&mhdp->mbox_mutex);
|
|
-
|
|
for (i = 0; i < 4; i++) {
|
|
msg[0] = block / 2;
|
|
msg[1] = block % 2;
|
|
|
|
- ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX,
|
|
- DPTX_GET_EDID, sizeof(msg), msg);
|
|
- if (ret)
|
|
- continue;
|
|
-
|
|
- ret = cdns_mhdp_mailbox_recv_header(mhdp, MB_MODULE_ID_DP_TX,
|
|
- DPTX_GET_EDID,
|
|
- sizeof(reg) + length);
|
|
- if (ret)
|
|
- continue;
|
|
-
|
|
- ret = cdns_mhdp_mailbox_recv_data(mhdp, reg, sizeof(reg));
|
|
- if (ret)
|
|
- continue;
|
|
-
|
|
- ret = cdns_mhdp_mailbox_recv_data(mhdp, edid, length);
|
|
+ ret = cdns_mhdp_mailbox_send_recv_multi(&mhdp->base,
|
|
+ MB_MODULE_ID_DP_TX,
|
|
+ DPTX_GET_EDID,
|
|
+ sizeof(msg), msg,
|
|
+ DPTX_GET_EDID,
|
|
+ sizeof(reg), reg,
|
|
+ length, edid);
|
|
if (ret)
|
|
continue;
|
|
|
|
@@ -447,8 +136,6 @@ int cdns_mhdp_get_edid_block(void *data, u8 *edid,
|
|
break;
|
|
}
|
|
|
|
- mutex_unlock(&mhdp->mbox_mutex);
|
|
-
|
|
if (ret)
|
|
dev_err(mhdp->dev, "get block[%d] edid failed: %d\n",
|
|
block, ret);
|
|
@@ -462,21 +149,9 @@ int cdns_mhdp_read_hpd_event(struct cdns_mhdp_device *mhdp)
|
|
u8 event = 0;
|
|
int ret;
|
|
|
|
- mutex_lock(&mhdp->mbox_mutex);
|
|
-
|
|
- ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX,
|
|
- DPTX_READ_EVENT, 0, NULL);
|
|
- if (ret)
|
|
- goto out;
|
|
-
|
|
- ret = cdns_mhdp_mailbox_recv_header(mhdp, MB_MODULE_ID_DP_TX,
|
|
- DPTX_READ_EVENT, sizeof(event));
|
|
- if (ret < 0)
|
|
- goto out;
|
|
-
|
|
- ret = cdns_mhdp_mailbox_recv_data(mhdp, &event, sizeof(event));
|
|
-out:
|
|
- mutex_unlock(&mhdp->mbox_mutex);
|
|
+ ret = cdns_mhdp_mailbox_send_recv(&mhdp->base, MB_MODULE_ID_DP_TX,
|
|
+ DPTX_READ_EVENT,
|
|
+ 0, NULL, sizeof(event), &event);
|
|
|
|
if (ret < 0)
|
|
return ret;
|
|
@@ -510,35 +185,23 @@ int cdns_mhdp_adjust_lt(struct cdns_mhdp_device *mhdp, unsigned int nlanes,
|
|
put_unaligned_be16(udelay, payload + 1);
|
|
memcpy(payload + 3, lanes_data, nlanes);
|
|
|
|
- mutex_lock(&mhdp->mbox_mutex);
|
|
-
|
|
- ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX,
|
|
- DPTX_ADJUST_LT,
|
|
- sizeof(payload), payload);
|
|
- if (ret)
|
|
- goto out;
|
|
-
|
|
/* Yes, read the DPCD read command response */
|
|
- ret = cdns_mhdp_mailbox_recv_header(mhdp, MB_MODULE_ID_DP_TX,
|
|
- DPTX_READ_DPCD,
|
|
- sizeof(hdr) + DP_LINK_STATUS_SIZE);
|
|
- if (ret)
|
|
- goto out;
|
|
-
|
|
- ret = cdns_mhdp_mailbox_recv_data(mhdp, hdr, sizeof(hdr));
|
|
+ ret = cdns_mhdp_mailbox_send_recv_multi(&mhdp->base,
|
|
+ MB_MODULE_ID_DP_TX,
|
|
+ DPTX_ADJUST_LT,
|
|
+ sizeof(payload), payload,
|
|
+ DPTX_READ_DPCD,
|
|
+ sizeof(hdr), hdr,
|
|
+ DP_LINK_STATUS_SIZE,
|
|
+ link_status);
|
|
if (ret)
|
|
goto out;
|
|
|
|
addr = get_unaligned_be24(hdr + 2);
|
|
if (addr != DP_LANE0_1_STATUS)
|
|
- goto out;
|
|
-
|
|
- ret = cdns_mhdp_mailbox_recv_data(mhdp, link_status,
|
|
- DP_LINK_STATUS_SIZE);
|
|
+ ret = -EINVAL;
|
|
|
|
out:
|
|
- mutex_unlock(&mhdp->mbox_mutex);
|
|
-
|
|
if (ret)
|
|
dev_err(mhdp->dev, "Failed to adjust Link Training.\n");
|
|
|
|
@@ -847,7 +510,7 @@ static ssize_t cdns_mhdp_transfer(struct drm_dp_aux *aux,
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < msg->size; ++i) {
|
|
- ret = cdns_mhdp_dpcd_write(mhdp,
|
|
+ ret = cdns_mhdp_dpcd_write(&mhdp->base,
|
|
msg->address + i, buf[i]);
|
|
if (!ret)
|
|
continue;
|
|
@@ -859,7 +522,7 @@ static ssize_t cdns_mhdp_transfer(struct drm_dp_aux *aux,
|
|
return ret;
|
|
}
|
|
} else {
|
|
- ret = cdns_mhdp_dpcd_read(mhdp, msg->address,
|
|
+ ret = cdns_mhdp_dpcd_read(&mhdp->base, msg->address,
|
|
msg->buffer, msg->size);
|
|
if (ret) {
|
|
dev_err(mhdp->dev,
|
|
@@ -887,12 +550,12 @@ static int cdns_mhdp_link_training_init(struct cdns_mhdp_device *mhdp)
|
|
if (!mhdp->host.scrambler)
|
|
reg32 |= CDNS_PHY_SCRAMBLER_BYPASS;
|
|
|
|
- cdns_mhdp_reg_write(mhdp, CDNS_DPTX_PHY_CONFIG, reg32);
|
|
+ cdns_mhdp_reg_write(&mhdp->base, CDNS_DPTX_PHY_CONFIG, reg32);
|
|
|
|
- cdns_mhdp_reg_write(mhdp, CDNS_DP_ENHNCD,
|
|
+ cdns_mhdp_reg_write(&mhdp->base, CDNS_DP_ENHNCD,
|
|
mhdp->sink.enhanced & mhdp->host.enhanced);
|
|
|
|
- cdns_mhdp_reg_write(mhdp, CDNS_DP_LANE_EN,
|
|
+ cdns_mhdp_reg_write(&mhdp->base, CDNS_DP_LANE_EN,
|
|
CDNS_DP_LANE_EN_LANES(mhdp->link.num_lanes));
|
|
|
|
cdns_mhdp_link_configure(&mhdp->aux, &mhdp->link);
|
|
@@ -913,7 +576,7 @@ static int cdns_mhdp_link_training_init(struct cdns_mhdp_device *mhdp)
|
|
return ret;
|
|
}
|
|
|
|
- cdns_mhdp_reg_write(mhdp, CDNS_DPTX_PHY_CONFIG,
|
|
+ cdns_mhdp_reg_write(&mhdp->base, CDNS_DPTX_PHY_CONFIG,
|
|
CDNS_PHY_COMMON_CONFIG |
|
|
CDNS_PHY_TRAINING_EN |
|
|
CDNS_PHY_TRAINING_TYPE(1) |
|
|
@@ -1058,7 +721,7 @@ static bool cdns_mhdp_link_training_channel_eq(struct cdns_mhdp_device *mhdp,
|
|
CDNS_PHY_TRAINING_TYPE(eq_tps);
|
|
if (eq_tps != 4)
|
|
reg32 |= CDNS_PHY_SCRAMBLER_BYPASS;
|
|
- cdns_mhdp_reg_write(mhdp, CDNS_DPTX_PHY_CONFIG, reg32);
|
|
+ cdns_mhdp_reg_write(&mhdp->base, CDNS_DPTX_PHY_CONFIG, reg32);
|
|
|
|
drm_dp_dpcd_writeb(&mhdp->aux, DP_TRAINING_PATTERN_SET,
|
|
(eq_tps != 4) ? eq_tps | DP_LINK_SCRAMBLING_DISABLE :
|
|
@@ -1322,7 +985,7 @@ static int cdns_mhdp_link_training(struct cdns_mhdp_device *mhdp,
|
|
mhdp->host.scrambler ? 0 :
|
|
DP_LINK_SCRAMBLING_DISABLE);
|
|
|
|
- ret = cdns_mhdp_reg_read(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, ®32);
|
|
+ ret = cdns_mhdp_reg_read(&mhdp->base, CDNS_DP_FRAMER_GLOBAL_CONFIG, ®32);
|
|
if (ret < 0) {
|
|
dev_err(mhdp->dev,
|
|
"Failed to read CDNS_DP_FRAMER_GLOBAL_CONFIG %d\n",
|
|
@@ -1333,13 +996,13 @@ static int cdns_mhdp_link_training(struct cdns_mhdp_device *mhdp,
|
|
reg32 |= CDNS_DP_NUM_LANES(mhdp->link.num_lanes);
|
|
reg32 |= CDNS_DP_WR_FAILING_EDGE_VSYNC;
|
|
reg32 |= CDNS_DP_FRAMER_EN;
|
|
- cdns_mhdp_reg_write(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, reg32);
|
|
+ cdns_mhdp_reg_write(&mhdp->base, CDNS_DP_FRAMER_GLOBAL_CONFIG, reg32);
|
|
|
|
/* Reset PHY config */
|
|
reg32 = CDNS_PHY_COMMON_CONFIG | CDNS_PHY_TRAINING_TYPE(1);
|
|
if (!mhdp->host.scrambler)
|
|
reg32 |= CDNS_PHY_SCRAMBLER_BYPASS;
|
|
- cdns_mhdp_reg_write(mhdp, CDNS_DPTX_PHY_CONFIG, reg32);
|
|
+ cdns_mhdp_reg_write(&mhdp->base, CDNS_DPTX_PHY_CONFIG, reg32);
|
|
|
|
return 0;
|
|
err:
|
|
@@ -1347,7 +1010,7 @@ static int cdns_mhdp_link_training(struct cdns_mhdp_device *mhdp,
|
|
reg32 = CDNS_PHY_COMMON_CONFIG | CDNS_PHY_TRAINING_TYPE(1);
|
|
if (!mhdp->host.scrambler)
|
|
reg32 |= CDNS_PHY_SCRAMBLER_BYPASS;
|
|
- cdns_mhdp_reg_write(mhdp, CDNS_DPTX_PHY_CONFIG, reg32);
|
|
+ cdns_mhdp_reg_write(&mhdp->base, CDNS_DPTX_PHY_CONFIG, reg32);
|
|
|
|
drm_dp_dpcd_writeb(&mhdp->aux, DP_TRAINING_PATTERN_SET,
|
|
DP_TRAINING_PATTERN_DISABLE);
|
|
@@ -1461,7 +1124,7 @@ static int cdns_mhdp_link_up(struct cdns_mhdp_device *mhdp)
|
|
mhdp->link.num_lanes = cdns_mhdp_max_num_lanes(mhdp);
|
|
|
|
/* Disable framer for link training */
|
|
- err = cdns_mhdp_reg_read(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, &resp);
|
|
+ err = cdns_mhdp_reg_read(&mhdp->base, CDNS_DP_FRAMER_GLOBAL_CONFIG, &resp);
|
|
if (err < 0) {
|
|
dev_err(mhdp->dev,
|
|
"Failed to read CDNS_DP_FRAMER_GLOBAL_CONFIG %d\n",
|
|
@@ -1470,7 +1133,7 @@ static int cdns_mhdp_link_up(struct cdns_mhdp_device *mhdp)
|
|
}
|
|
|
|
resp &= ~CDNS_DP_FRAMER_EN;
|
|
- cdns_mhdp_reg_write(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, resp);
|
|
+ cdns_mhdp_reg_write(&mhdp->base, CDNS_DP_FRAMER_GLOBAL_CONFIG, resp);
|
|
|
|
/* Spread AMP if required, enable 8b/10b coding */
|
|
amp[0] = cdns_mhdp_get_ssc_supported(mhdp) ? DP_SPREAD_AMP_0_5 : 0;
|
|
@@ -1834,7 +1497,7 @@ static void cdns_mhdp_configure_video(struct cdns_mhdp_device *mhdp,
|
|
if (mode->flags & DRM_MODE_FLAG_INTERLACE)
|
|
bnd_hsync2vsync |= CDNS_IP_DET_INTERLACE_FORMAT;
|
|
|
|
- cdns_mhdp_reg_write(mhdp, CDNS_BND_HSYNC2VSYNC(stream_id),
|
|
+ cdns_mhdp_reg_write(&mhdp->base, CDNS_BND_HSYNC2VSYNC(stream_id),
|
|
bnd_hsync2vsync);
|
|
|
|
hsync2vsync_pol_ctrl = 0;
|
|
@@ -1842,10 +1505,10 @@ static void cdns_mhdp_configure_video(struct cdns_mhdp_device *mhdp,
|
|
hsync2vsync_pol_ctrl |= CDNS_H2V_HSYNC_POL_ACTIVE_LOW;
|
|
if (mode->flags & DRM_MODE_FLAG_NVSYNC)
|
|
hsync2vsync_pol_ctrl |= CDNS_H2V_VSYNC_POL_ACTIVE_LOW;
|
|
- cdns_mhdp_reg_write(mhdp, CDNS_HSYNC2VSYNC_POL_CTRL(stream_id),
|
|
+ cdns_mhdp_reg_write(&mhdp->base, CDNS_HSYNC2VSYNC_POL_CTRL(stream_id),
|
|
hsync2vsync_pol_ctrl);
|
|
|
|
- cdns_mhdp_reg_write(mhdp, CDNS_DP_FRAMER_PXL_REPR(stream_id), pxl_repr);
|
|
+ cdns_mhdp_reg_write(&mhdp->base, CDNS_DP_FRAMER_PXL_REPR(stream_id), pxl_repr);
|
|
|
|
if (mode->flags & DRM_MODE_FLAG_INTERLACE)
|
|
dp_framer_sp |= CDNS_DP_FRAMER_INTERLACE;
|
|
@@ -1853,19 +1516,19 @@ static void cdns_mhdp_configure_video(struct cdns_mhdp_device *mhdp,
|
|
dp_framer_sp |= CDNS_DP_FRAMER_HSYNC_POL_LOW;
|
|
if (mode->flags & DRM_MODE_FLAG_NVSYNC)
|
|
dp_framer_sp |= CDNS_DP_FRAMER_VSYNC_POL_LOW;
|
|
- cdns_mhdp_reg_write(mhdp, CDNS_DP_FRAMER_SP(stream_id), dp_framer_sp);
|
|
+ cdns_mhdp_reg_write(&mhdp->base, CDNS_DP_FRAMER_SP(stream_id), dp_framer_sp);
|
|
|
|
front_porch = mode->crtc_hsync_start - mode->crtc_hdisplay;
|
|
back_porch = mode->crtc_htotal - mode->crtc_hsync_end;
|
|
- cdns_mhdp_reg_write(mhdp, CDNS_DP_FRONT_BACK_PORCH(stream_id),
|
|
+ cdns_mhdp_reg_write(&mhdp->base, CDNS_DP_FRONT_BACK_PORCH(stream_id),
|
|
CDNS_DP_FRONT_PORCH(front_porch) |
|
|
CDNS_DP_BACK_PORCH(back_porch));
|
|
|
|
- cdns_mhdp_reg_write(mhdp, CDNS_DP_BYTE_COUNT(stream_id),
|
|
+ cdns_mhdp_reg_write(&mhdp->base, CDNS_DP_BYTE_COUNT(stream_id),
|
|
mode->crtc_hdisplay * bpp / 8);
|
|
|
|
msa_h0 = mode->crtc_htotal - mode->crtc_hsync_start;
|
|
- cdns_mhdp_reg_write(mhdp, CDNS_DP_MSA_HORIZONTAL_0(stream_id),
|
|
+ cdns_mhdp_reg_write(&mhdp->base, CDNS_DP_MSA_HORIZONTAL_0(stream_id),
|
|
CDNS_DP_MSAH0_H_TOTAL(mode->crtc_htotal) |
|
|
CDNS_DP_MSAH0_HSYNC_START(msa_h0));
|
|
|
|
@@ -1874,11 +1537,11 @@ static void cdns_mhdp_configure_video(struct cdns_mhdp_device *mhdp,
|
|
CDNS_DP_MSAH1_HDISP_WIDTH(mode->crtc_hdisplay);
|
|
if (mode->flags & DRM_MODE_FLAG_NHSYNC)
|
|
msa_horizontal_1 |= CDNS_DP_MSAH1_HSYNC_POL_LOW;
|
|
- cdns_mhdp_reg_write(mhdp, CDNS_DP_MSA_HORIZONTAL_1(stream_id),
|
|
+ cdns_mhdp_reg_write(&mhdp->base, CDNS_DP_MSA_HORIZONTAL_1(stream_id),
|
|
msa_horizontal_1);
|
|
|
|
msa_v0 = mode->crtc_vtotal - mode->crtc_vsync_start;
|
|
- cdns_mhdp_reg_write(mhdp, CDNS_DP_MSA_VERTICAL_0(stream_id),
|
|
+ cdns_mhdp_reg_write(&mhdp->base, CDNS_DP_MSA_VERTICAL_0(stream_id),
|
|
CDNS_DP_MSAV0_V_TOTAL(mode->crtc_vtotal) |
|
|
CDNS_DP_MSAV0_VSYNC_START(msa_v0));
|
|
|
|
@@ -1887,7 +1550,7 @@ static void cdns_mhdp_configure_video(struct cdns_mhdp_device *mhdp,
|
|
CDNS_DP_MSAV1_VDISP_WIDTH(mode->crtc_vdisplay);
|
|
if (mode->flags & DRM_MODE_FLAG_NVSYNC)
|
|
msa_vertical_1 |= CDNS_DP_MSAV1_VSYNC_POL_LOW;
|
|
- cdns_mhdp_reg_write(mhdp, CDNS_DP_MSA_VERTICAL_1(stream_id),
|
|
+ cdns_mhdp_reg_write(&mhdp->base, CDNS_DP_MSA_VERTICAL_1(stream_id),
|
|
msa_vertical_1);
|
|
|
|
if ((mode->flags & DRM_MODE_FLAG_INTERLACE) &&
|
|
@@ -1899,14 +1562,14 @@ static void cdns_mhdp_configure_video(struct cdns_mhdp_device *mhdp,
|
|
if (pxlfmt == DRM_COLOR_FORMAT_YCBCR420)
|
|
misc1 = CDNS_DP_TEST_VSC_SDP;
|
|
|
|
- cdns_mhdp_reg_write(mhdp, CDNS_DP_MSA_MISC(stream_id),
|
|
+ cdns_mhdp_reg_write(&mhdp->base, CDNS_DP_MSA_MISC(stream_id),
|
|
misc0 | (misc1 << 8));
|
|
|
|
- cdns_mhdp_reg_write(mhdp, CDNS_DP_HORIZONTAL(stream_id),
|
|
+ cdns_mhdp_reg_write(&mhdp->base, CDNS_DP_HORIZONTAL(stream_id),
|
|
CDNS_DP_H_HSYNC_WIDTH(hsync) |
|
|
CDNS_DP_H_H_TOTAL(mode->crtc_hdisplay));
|
|
|
|
- cdns_mhdp_reg_write(mhdp, CDNS_DP_VERTICAL_0(stream_id),
|
|
+ cdns_mhdp_reg_write(&mhdp->base, CDNS_DP_VERTICAL_0(stream_id),
|
|
CDNS_DP_V0_VHEIGHT(mode->crtc_vdisplay) |
|
|
CDNS_DP_V0_VSTART(msa_v0));
|
|
|
|
@@ -1915,13 +1578,13 @@ static void cdns_mhdp_configure_video(struct cdns_mhdp_device *mhdp,
|
|
mode->crtc_vtotal % 2 == 0)
|
|
dp_vertical_1 |= CDNS_DP_V1_VTOTAL_EVEN;
|
|
|
|
- cdns_mhdp_reg_write(mhdp, CDNS_DP_VERTICAL_1(stream_id), dp_vertical_1);
|
|
+ cdns_mhdp_reg_write(&mhdp->base, CDNS_DP_VERTICAL_1(stream_id), dp_vertical_1);
|
|
|
|
- cdns_mhdp_reg_write_bit(mhdp, CDNS_DP_VB_ID(stream_id), 2, 1,
|
|
- (mode->flags & DRM_MODE_FLAG_INTERLACE) ?
|
|
- CDNS_DP_VB_ID_INTERLACED : 0);
|
|
+ cdns_mhdp_dp_reg_write_bit(&mhdp->base, CDNS_DP_VB_ID(stream_id), 2, 1,
|
|
+ (mode->flags & DRM_MODE_FLAG_INTERLACE) ?
|
|
+ CDNS_DP_VB_ID_INTERLACED : 0);
|
|
|
|
- ret = cdns_mhdp_reg_read(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, &framer);
|
|
+ ret = cdns_mhdp_reg_read(&mhdp->base, CDNS_DP_FRAMER_GLOBAL_CONFIG, &framer);
|
|
if (ret < 0) {
|
|
dev_err(mhdp->dev,
|
|
"Failed to read CDNS_DP_FRAMER_GLOBAL_CONFIG %d\n",
|
|
@@ -1930,7 +1593,7 @@ static void cdns_mhdp_configure_video(struct cdns_mhdp_device *mhdp,
|
|
}
|
|
framer |= CDNS_DP_FRAMER_EN;
|
|
framer &= ~CDNS_DP_NO_VIDEO_MODE;
|
|
- cdns_mhdp_reg_write(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, framer);
|
|
+ cdns_mhdp_reg_write(&mhdp->base, CDNS_DP_FRAMER_GLOBAL_CONFIG, framer);
|
|
}
|
|
|
|
static void cdns_mhdp_sst_enable(struct cdns_mhdp_device *mhdp,
|
|
@@ -1963,15 +1626,15 @@ static void cdns_mhdp_sst_enable(struct cdns_mhdp_device *mhdp,
|
|
|
|
mhdp->stream_id = 0;
|
|
|
|
- cdns_mhdp_reg_write(mhdp, CDNS_DP_FRAMER_TU,
|
|
+ cdns_mhdp_reg_write(&mhdp->base, CDNS_DP_FRAMER_TU,
|
|
CDNS_DP_FRAMER_TU_VS(vs) |
|
|
CDNS_DP_FRAMER_TU_SIZE(tu_size) |
|
|
CDNS_DP_FRAMER_TU_CNT_RST_EN);
|
|
|
|
- cdns_mhdp_reg_write(mhdp, CDNS_DP_LINE_THRESH(0),
|
|
+ cdns_mhdp_reg_write(&mhdp->base, CDNS_DP_LINE_THRESH(0),
|
|
line_thresh & GENMASK(5, 0));
|
|
|
|
- cdns_mhdp_reg_write(mhdp, CDNS_DP_STREAM_CONFIG_2(0),
|
|
+ cdns_mhdp_reg_write(&mhdp->base, CDNS_DP_STREAM_CONFIG_2(0),
|
|
CDNS_DP_SC2_TU_VS_DIFF((tu_size - vs > 3) ?
|
|
0 : tu_size - vs));
|
|
|
|
@@ -2006,13 +1669,13 @@ static void cdns_mhdp_atomic_enable(struct drm_bridge *bridge,
|
|
mhdp->info->ops->enable(mhdp);
|
|
|
|
/* Enable VIF clock for stream 0 */
|
|
- ret = cdns_mhdp_reg_read(mhdp, CDNS_DPTX_CAR, &resp);
|
|
+ ret = cdns_mhdp_reg_read(&mhdp->base, CDNS_DPTX_CAR, &resp);
|
|
if (ret < 0) {
|
|
dev_err(mhdp->dev, "Failed to read CDNS_DPTX_CAR %d\n", ret);
|
|
goto out;
|
|
}
|
|
|
|
- cdns_mhdp_reg_write(mhdp, CDNS_DPTX_CAR,
|
|
+ cdns_mhdp_reg_write(&mhdp->base, CDNS_DPTX_CAR,
|
|
resp | CDNS_VIF_CLK_EN | CDNS_VIF_CLK_RSTN);
|
|
|
|
connector = drm_atomic_get_new_connector_for_encoder(state,
|
|
@@ -2083,16 +1746,16 @@ static void cdns_mhdp_atomic_disable(struct drm_bridge *bridge,
|
|
cdns_mhdp_hdcp_disable(mhdp);
|
|
|
|
mhdp->bridge_enabled = false;
|
|
- cdns_mhdp_reg_read(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, &resp);
|
|
+ cdns_mhdp_reg_read(&mhdp->base, CDNS_DP_FRAMER_GLOBAL_CONFIG, &resp);
|
|
resp &= ~CDNS_DP_FRAMER_EN;
|
|
resp |= CDNS_DP_NO_VIDEO_MODE;
|
|
- cdns_mhdp_reg_write(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, resp);
|
|
+ cdns_mhdp_reg_write(&mhdp->base, CDNS_DP_FRAMER_GLOBAL_CONFIG, resp);
|
|
|
|
cdns_mhdp_link_down(mhdp);
|
|
|
|
/* Disable VIF clock for stream 0 */
|
|
- cdns_mhdp_reg_read(mhdp, CDNS_DPTX_CAR, &resp);
|
|
- cdns_mhdp_reg_write(mhdp, CDNS_DPTX_CAR,
|
|
+ cdns_mhdp_reg_read(&mhdp->base, CDNS_DPTX_CAR, &resp);
|
|
+ cdns_mhdp_reg_write(&mhdp->base, CDNS_DPTX_CAR,
|
|
resp & ~(CDNS_VIF_CLK_EN | CDNS_VIF_CLK_RSTN));
|
|
|
|
if (mhdp->info && mhdp->info->ops && mhdp->info->ops->disable)
|
|
@@ -2471,7 +2134,6 @@ static int cdns_mhdp_probe(struct platform_device *pdev)
|
|
|
|
mhdp->clk = clk;
|
|
mhdp->dev = dev;
|
|
- mutex_init(&mhdp->mbox_mutex);
|
|
mutex_init(&mhdp->link_mutex);
|
|
spin_lock_init(&mhdp->start_lock);
|
|
|
|
@@ -2502,6 +2164,11 @@ static int cdns_mhdp_probe(struct platform_device *pdev)
|
|
|
|
platform_set_drvdata(pdev, mhdp);
|
|
|
|
+ /* init base struct for access mailbox */
|
|
+ mhdp->base.dev = mhdp->dev;
|
|
+ mhdp->base.regs = mhdp->regs;
|
|
+ mhdp->base.sapb_regs = mhdp->sapb_regs;
|
|
+
|
|
mhdp->info = of_device_get_match_data(dev);
|
|
|
|
clk_prepare_enable(clk);
|
|
diff --git a/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.h b/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.h
|
|
index bad2fc0c73066..535300d040dea 100644
|
|
--- a/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.h
|
|
+++ b/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.h
|
|
@@ -18,6 +18,7 @@
|
|
#include <drm/display/drm_dp_helper.h>
|
|
#include <drm/drm_bridge.h>
|
|
#include <drm/drm_connector.h>
|
|
+#include <soc/cadence/cdns-mhdp-helper.h>
|
|
|
|
struct clk;
|
|
struct device;
|
|
@@ -27,10 +28,6 @@ struct phy;
|
|
#define CDNS_APB_CTRL 0x00000
|
|
#define CDNS_CPU_STALL BIT(3)
|
|
|
|
-#define CDNS_MAILBOX_FULL 0x00008
|
|
-#define CDNS_MAILBOX_EMPTY 0x0000c
|
|
-#define CDNS_MAILBOX_TX_DATA 0x00010
|
|
-#define CDNS_MAILBOX_RX_DATA 0x00014
|
|
#define CDNS_KEEP_ALIVE 0x00018
|
|
#define CDNS_KEEP_ALIVE_MASK GENMASK(7, 0)
|
|
|
|
@@ -198,45 +195,10 @@ struct phy;
|
|
#define CDNS_DP_BYTE_COUNT(s) (CDNS_DPTX_STREAM(s) + 0x7c)
|
|
#define CDNS_DP_BYTE_COUNT_BYTES_IN_CHUNK_SHIFT 16
|
|
|
|
-/* mailbox */
|
|
-#define MAILBOX_RETRY_US 1000
|
|
-#define MAILBOX_TIMEOUT_US 2000000
|
|
-
|
|
-#define MB_OPCODE_ID 0
|
|
-#define MB_MODULE_ID 1
|
|
-#define MB_SIZE_MSB_ID 2
|
|
-#define MB_SIZE_LSB_ID 3
|
|
-#define MB_DATA_ID 4
|
|
-
|
|
-#define MB_MODULE_ID_DP_TX 0x01
|
|
-#define MB_MODULE_ID_HDCP_TX 0x07
|
|
-#define MB_MODULE_ID_HDCP_RX 0x08
|
|
-#define MB_MODULE_ID_HDCP_GENERAL 0x09
|
|
-#define MB_MODULE_ID_GENERAL 0x0a
|
|
-
|
|
-/* firmware and opcodes */
|
|
+/* firmware */
|
|
#define FW_NAME "cadence/mhdp8546.bin"
|
|
#define CDNS_MHDP_IMEM 0x10000
|
|
|
|
-#define GENERAL_MAIN_CONTROL 0x01
|
|
-#define GENERAL_TEST_ECHO 0x02
|
|
-#define GENERAL_BUS_SETTINGS 0x03
|
|
-#define GENERAL_TEST_ACCESS 0x04
|
|
-#define GENERAL_REGISTER_READ 0x07
|
|
-
|
|
-#define DPTX_SET_POWER_MNG 0x00
|
|
-#define DPTX_GET_EDID 0x02
|
|
-#define DPTX_READ_DPCD 0x03
|
|
-#define DPTX_WRITE_DPCD 0x04
|
|
-#define DPTX_ENABLE_EVENT 0x05
|
|
-#define DPTX_WRITE_REGISTER 0x06
|
|
-#define DPTX_READ_REGISTER 0x07
|
|
-#define DPTX_WRITE_FIELD 0x08
|
|
-#define DPTX_READ_EVENT 0x0a
|
|
-#define DPTX_GET_LAST_AUX_STAUS 0x0e
|
|
-#define DPTX_HPD_STATE 0x11
|
|
-#define DPTX_ADJUST_LT 0x12
|
|
-
|
|
#define FW_STANDBY 0
|
|
#define FW_ACTIVE 1
|
|
|
|
@@ -352,6 +314,8 @@ struct cdns_mhdp_hdcp {
|
|
};
|
|
|
|
struct cdns_mhdp_device {
|
|
+ struct cdns_mhdp_base base;
|
|
+
|
|
void __iomem *regs;
|
|
void __iomem *sapb_regs;
|
|
void __iomem *j721e_regs;
|
|
@@ -362,9 +326,6 @@ struct cdns_mhdp_device {
|
|
|
|
const struct cdns_mhdp_platform_info *info;
|
|
|
|
- /* This is to protect mailbox communications with the firmware */
|
|
- struct mutex mbox_mutex;
|
|
-
|
|
/*
|
|
* "link_mutex" protects the access to all the link parameters
|
|
* including the link training process. Link training will be
|
|
diff --git a/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-hdcp.c b/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-hdcp.c
|
|
index 42248f179b69d..3944642f2ebbc 100644
|
|
--- a/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-hdcp.c
|
|
+++ b/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-hdcp.c
|
|
@@ -15,144 +15,20 @@
|
|
|
|
#include "cdns-mhdp8546-hdcp.h"
|
|
|
|
-static int cdns_mhdp_secure_mailbox_read(struct cdns_mhdp_device *mhdp)
|
|
-{
|
|
- int ret, empty;
|
|
-
|
|
- WARN_ON(!mutex_is_locked(&mhdp->mbox_mutex));
|
|
-
|
|
- ret = readx_poll_timeout(readl, mhdp->sapb_regs + CDNS_MAILBOX_EMPTY,
|
|
- empty, !empty, MAILBOX_RETRY_US,
|
|
- MAILBOX_TIMEOUT_US);
|
|
- if (ret < 0)
|
|
- return ret;
|
|
-
|
|
- return readl(mhdp->sapb_regs + CDNS_MAILBOX_RX_DATA) & 0xff;
|
|
-}
|
|
-
|
|
-static int cdns_mhdp_secure_mailbox_write(struct cdns_mhdp_device *mhdp,
|
|
- u8 val)
|
|
-{
|
|
- int ret, full;
|
|
-
|
|
- WARN_ON(!mutex_is_locked(&mhdp->mbox_mutex));
|
|
-
|
|
- ret = readx_poll_timeout(readl, mhdp->sapb_regs + CDNS_MAILBOX_FULL,
|
|
- full, !full, MAILBOX_RETRY_US,
|
|
- MAILBOX_TIMEOUT_US);
|
|
- if (ret < 0)
|
|
- return ret;
|
|
-
|
|
- writel(val, mhdp->sapb_regs + CDNS_MAILBOX_TX_DATA);
|
|
-
|
|
- return 0;
|
|
-}
|
|
-
|
|
-static int cdns_mhdp_secure_mailbox_recv_header(struct cdns_mhdp_device *mhdp,
|
|
- u8 module_id,
|
|
- u8 opcode,
|
|
- u16 req_size)
|
|
-{
|
|
- u32 mbox_size, i;
|
|
- u8 header[4];
|
|
- int ret;
|
|
-
|
|
- /* read the header of the message */
|
|
- for (i = 0; i < sizeof(header); i++) {
|
|
- ret = cdns_mhdp_secure_mailbox_read(mhdp);
|
|
- if (ret < 0)
|
|
- return ret;
|
|
-
|
|
- header[i] = ret;
|
|
- }
|
|
-
|
|
- mbox_size = get_unaligned_be16(header + 2);
|
|
-
|
|
- if (opcode != header[0] || module_id != header[1] ||
|
|
- (opcode != HDCP_TRAN_IS_REC_ID_VALID && req_size != mbox_size)) {
|
|
- for (i = 0; i < mbox_size; i++)
|
|
- if (cdns_mhdp_secure_mailbox_read(mhdp) < 0)
|
|
- break;
|
|
- return -EINVAL;
|
|
- }
|
|
-
|
|
- return 0;
|
|
-}
|
|
-
|
|
-static int cdns_mhdp_secure_mailbox_recv_data(struct cdns_mhdp_device *mhdp,
|
|
- u8 *buff, u16 buff_size)
|
|
-{
|
|
- int ret;
|
|
- u32 i;
|
|
-
|
|
- for (i = 0; i < buff_size; i++) {
|
|
- ret = cdns_mhdp_secure_mailbox_read(mhdp);
|
|
- if (ret < 0)
|
|
- return ret;
|
|
-
|
|
- buff[i] = ret;
|
|
- }
|
|
-
|
|
- return 0;
|
|
-}
|
|
-
|
|
-static int cdns_mhdp_secure_mailbox_send(struct cdns_mhdp_device *mhdp,
|
|
- u8 module_id,
|
|
- u8 opcode,
|
|
- u16 size,
|
|
- u8 *message)
|
|
-{
|
|
- u8 header[4];
|
|
- int ret;
|
|
- u32 i;
|
|
-
|
|
- header[0] = opcode;
|
|
- header[1] = module_id;
|
|
- put_unaligned_be16(size, header + 2);
|
|
-
|
|
- for (i = 0; i < sizeof(header); i++) {
|
|
- ret = cdns_mhdp_secure_mailbox_write(mhdp, header[i]);
|
|
- if (ret)
|
|
- return ret;
|
|
- }
|
|
-
|
|
- for (i = 0; i < size; i++) {
|
|
- ret = cdns_mhdp_secure_mailbox_write(mhdp, message[i]);
|
|
- if (ret)
|
|
- return ret;
|
|
- }
|
|
-
|
|
- return 0;
|
|
-}
|
|
-
|
|
static int cdns_mhdp_hdcp_get_status(struct cdns_mhdp_device *mhdp,
|
|
u16 *hdcp_port_status)
|
|
{
|
|
u8 hdcp_status[HDCP_STATUS_SIZE];
|
|
int ret;
|
|
|
|
- mutex_lock(&mhdp->mbox_mutex);
|
|
- ret = cdns_mhdp_secure_mailbox_send(mhdp, MB_MODULE_ID_HDCP_TX,
|
|
- HDCP_TRAN_STATUS_CHANGE, 0, NULL);
|
|
- if (ret)
|
|
- goto err_get_hdcp_status;
|
|
-
|
|
- ret = cdns_mhdp_secure_mailbox_recv_header(mhdp, MB_MODULE_ID_HDCP_TX,
|
|
- HDCP_TRAN_STATUS_CHANGE,
|
|
- sizeof(hdcp_status));
|
|
- if (ret)
|
|
- goto err_get_hdcp_status;
|
|
-
|
|
- ret = cdns_mhdp_secure_mailbox_recv_data(mhdp, hdcp_status,
|
|
- sizeof(hdcp_status));
|
|
+ ret = cdns_mhdp_secure_mailbox_send_recv(&mhdp->base, MB_MODULE_ID_HDCP_TX,
|
|
+ HDCP_TRAN_STATUS_CHANGE, 0, NULL,
|
|
+ sizeof(hdcp_status), hdcp_status);
|
|
if (ret)
|
|
- goto err_get_hdcp_status;
|
|
+ return ret;
|
|
|
|
*hdcp_port_status = ((u16)(hdcp_status[0] << 8) | hdcp_status[1]);
|
|
|
|
-err_get_hdcp_status:
|
|
- mutex_unlock(&mhdp->mbox_mutex);
|
|
-
|
|
return ret;
|
|
}
|
|
|
|
@@ -170,98 +46,52 @@ static u8 cdns_mhdp_hdcp_handle_status(struct cdns_mhdp_device *mhdp,
|
|
static int cdns_mhdp_hdcp_rx_id_valid_response(struct cdns_mhdp_device *mhdp,
|
|
u8 valid)
|
|
{
|
|
- int ret;
|
|
-
|
|
- mutex_lock(&mhdp->mbox_mutex);
|
|
- ret = cdns_mhdp_secure_mailbox_send(mhdp, MB_MODULE_ID_HDCP_TX,
|
|
+ return cdns_mhdp_secure_mailbox_send(&mhdp->base, MB_MODULE_ID_HDCP_TX,
|
|
HDCP_TRAN_RESPOND_RECEIVER_ID_VALID,
|
|
1, &valid);
|
|
- mutex_unlock(&mhdp->mbox_mutex);
|
|
-
|
|
- return ret;
|
|
}
|
|
|
|
static int cdns_mhdp_hdcp_rx_id_valid(struct cdns_mhdp_device *mhdp,
|
|
u8 *recv_num, u8 *hdcp_rx_id)
|
|
{
|
|
u8 rec_id_hdr[2];
|
|
- u8 status;
|
|
int ret;
|
|
|
|
- mutex_lock(&mhdp->mbox_mutex);
|
|
- ret = cdns_mhdp_secure_mailbox_send(mhdp, MB_MODULE_ID_HDCP_TX,
|
|
- HDCP_TRAN_IS_REC_ID_VALID, 0, NULL);
|
|
- if (ret)
|
|
- goto err_rx_id_valid;
|
|
-
|
|
- ret = cdns_mhdp_secure_mailbox_recv_header(mhdp, MB_MODULE_ID_HDCP_TX,
|
|
- HDCP_TRAN_IS_REC_ID_VALID,
|
|
- sizeof(status));
|
|
- if (ret)
|
|
- goto err_rx_id_valid;
|
|
-
|
|
- ret = cdns_mhdp_secure_mailbox_recv_data(mhdp, rec_id_hdr, 2);
|
|
+ ret = cdns_mhdp_secure_mailbox_send_recv_multi(&mhdp->base,
|
|
+ MB_MODULE_ID_HDCP_TX,
|
|
+ HDCP_TRAN_IS_REC_ID_VALID,
|
|
+ 0, NULL,
|
|
+ HDCP_TRAN_IS_REC_ID_VALID,
|
|
+ sizeof(rec_id_hdr), rec_id_hdr,
|
|
+ 0, hdcp_rx_id);
|
|
if (ret)
|
|
- goto err_rx_id_valid;
|
|
+ return ret;
|
|
|
|
*recv_num = rec_id_hdr[0];
|
|
|
|
- ret = cdns_mhdp_secure_mailbox_recv_data(mhdp, hdcp_rx_id, 5 * *recv_num);
|
|
-
|
|
-err_rx_id_valid:
|
|
- mutex_unlock(&mhdp->mbox_mutex);
|
|
-
|
|
- return ret;
|
|
+ return 0;
|
|
}
|
|
|
|
static int cdns_mhdp_hdcp_km_stored_resp(struct cdns_mhdp_device *mhdp,
|
|
u32 size, u8 *km)
|
|
{
|
|
- int ret;
|
|
-
|
|
- mutex_lock(&mhdp->mbox_mutex);
|
|
- ret = cdns_mhdp_secure_mailbox_send(mhdp, MB_MODULE_ID_HDCP_TX,
|
|
- HDCP2X_TX_RESPOND_KM, size, km);
|
|
- mutex_unlock(&mhdp->mbox_mutex);
|
|
-
|
|
- return ret;
|
|
+ return cdns_mhdp_secure_mailbox_send(&mhdp->base, MB_MODULE_ID_HDCP_TX,
|
|
+ HDCP2X_TX_RESPOND_KM, size, km);
|
|
}
|
|
|
|
static int cdns_mhdp_hdcp_tx_is_km_stored(struct cdns_mhdp_device *mhdp,
|
|
u8 *resp, u32 size)
|
|
{
|
|
- int ret;
|
|
-
|
|
- mutex_lock(&mhdp->mbox_mutex);
|
|
- ret = cdns_mhdp_secure_mailbox_send(mhdp, MB_MODULE_ID_HDCP_TX,
|
|
- HDCP2X_TX_IS_KM_STORED, 0, NULL);
|
|
- if (ret)
|
|
- goto err_is_km_stored;
|
|
-
|
|
- ret = cdns_mhdp_secure_mailbox_recv_header(mhdp, MB_MODULE_ID_HDCP_TX,
|
|
- HDCP2X_TX_IS_KM_STORED,
|
|
- size);
|
|
- if (ret)
|
|
- goto err_is_km_stored;
|
|
-
|
|
- ret = cdns_mhdp_secure_mailbox_recv_data(mhdp, resp, size);
|
|
-err_is_km_stored:
|
|
- mutex_unlock(&mhdp->mbox_mutex);
|
|
-
|
|
- return ret;
|
|
+ return cdns_mhdp_secure_mailbox_send_recv(&mhdp->base, MB_MODULE_ID_HDCP_TX,
|
|
+ HDCP2X_TX_IS_KM_STORED,
|
|
+ 0, NULL, size, resp);
|
|
}
|
|
|
|
static int cdns_mhdp_hdcp_tx_config(struct cdns_mhdp_device *mhdp,
|
|
u8 hdcp_cfg)
|
|
{
|
|
- int ret;
|
|
-
|
|
- mutex_lock(&mhdp->mbox_mutex);
|
|
- ret = cdns_mhdp_secure_mailbox_send(mhdp, MB_MODULE_ID_HDCP_TX,
|
|
- HDCP_TRAN_CONFIGURATION, 1, &hdcp_cfg);
|
|
- mutex_unlock(&mhdp->mbox_mutex);
|
|
-
|
|
- return ret;
|
|
+ return cdns_mhdp_secure_mailbox_send(&mhdp->base, MB_MODULE_ID_HDCP_TX,
|
|
+ HDCP_TRAN_CONFIGURATION, 1, &hdcp_cfg);
|
|
}
|
|
|
|
static int cdns_mhdp_hdcp_set_config(struct cdns_mhdp_device *mhdp,
|
|
diff --git a/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-hdcp.h b/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-hdcp.h
|
|
index 3b6ec9c3a8d8b..1e68530e72229 100644
|
|
--- a/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-hdcp.h
|
|
+++ b/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-hdcp.h
|
|
@@ -9,6 +9,7 @@
|
|
#ifndef CDNS_MHDP8546_HDCP_H
|
|
#define CDNS_MHDP8546_HDCP_H
|
|
|
|
+#include <soc/cadence/cdns-mhdp-helper.h>
|
|
#include "cdns-mhdp8546-core.h"
|
|
|
|
#define HDCP_MAX_RECEIVERS 32
|
|
@@ -32,23 +33,6 @@ enum {
|
|
HDCP_SET_SEED,
|
|
};
|
|
|
|
-enum {
|
|
- HDCP_TRAN_CONFIGURATION,
|
|
- HDCP2X_TX_SET_PUBLIC_KEY_PARAMS,
|
|
- HDCP2X_TX_SET_DEBUG_RANDOM_NUMBERS,
|
|
- HDCP2X_TX_RESPOND_KM,
|
|
- HDCP1_TX_SEND_KEYS,
|
|
- HDCP1_TX_SEND_RANDOM_AN,
|
|
- HDCP_TRAN_STATUS_CHANGE,
|
|
- HDCP2X_TX_IS_KM_STORED,
|
|
- HDCP2X_TX_STORE_KM,
|
|
- HDCP_TRAN_IS_REC_ID_VALID,
|
|
- HDCP_TRAN_RESPOND_RECEIVER_ID_VALID,
|
|
- HDCP_TRAN_TEST_KEYS,
|
|
- HDCP2X_TX_SET_KM_KEY_PARAMS,
|
|
- HDCP_NUM_OF_SUPPORTED_MESSAGES
|
|
-};
|
|
-
|
|
enum {
|
|
HDCP_CONTENT_TYPE_0,
|
|
HDCP_CONTENT_TYPE_1,
|
|
|
|
From patchwork Tue Dec 17 06:51:45 2024
|
|
Content-Type: text/plain; charset="utf-8"
|
|
MIME-Version: 1.0
|
|
Content-Transfer-Encoding: 7bit
|
|
Subject: [v20,3/9] phy: Add HDMI configuration options
|
|
From: Sandor Yu <Sandor.yu@nxp.com>
|
|
X-Patchwork-Id: 629290
|
|
Message-Id:
|
|
<d1cff6c03ec3732d2244022029245ab2d954d997.1734340233.git.Sandor.yu@nxp.com>
|
|
To: dmitry.baryshkov@linaro.org, andrzej.hajda@intel.com,
|
|
neil.armstrong@linaro.org, Laurent.pinchart@ideasonboard.com,
|
|
jonas@kwiboo.se, jernej.skrabec@gmail.com, airlied@gmail.com,
|
|
daniel@ffwll.ch, robh+dt@kernel.org, krzysztof.kozlowski+dt@linaro.org,
|
|
shawnguo@kernel.org, s.hauer@pengutronix.de, festevam@gmail.com,
|
|
vkoul@kernel.org, dri-devel@lists.freedesktop.org,
|
|
devicetree@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
|
|
linux-kernel@vger.kernel.org, linux-phy@lists.infradead.org,
|
|
mripard@kernel.org
|
|
Cc: kernel@pengutronix.de, linux-imx@nxp.com, Sandor.yu@nxp.com,
|
|
oliver.brown@nxp.com, alexander.stein@ew.tq-group.com, sam@ravnborg.org
|
|
Date: Tue, 17 Dec 2024 14:51:45 +0800
|
|
|
|
Allow HDMI PHYs to be configured through the generic
|
|
functions through a custom structure added to the generic union.
|
|
|
|
The parameters added here are based on HDMI PHY
|
|
implementation practices. The current set of parameters
|
|
should cover the potential users.
|
|
|
|
Signed-off-by: Sandor Yu <Sandor.yu@nxp.com>
|
|
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
|
|
Reviewed-by: Maxime Ripard <mripard@kernel.org>
|
|
Acked-by: Vinod Koul <vkoul@kernel.org>
|
|
---
|
|
v19->v20:
|
|
- Add a-b tag.
|
|
|
|
v17->v19:
|
|
*No change.
|
|
|
|
v16->v17:
|
|
- remove headfile hdmi.h
|
|
- add 2024 year to copyright
|
|
- Add r-b tag.
|
|
|
|
include/linux/phy/phy-hdmi.h | 19 +++++++++++++++++++
|
|
include/linux/phy/phy.h | 7 ++++++-
|
|
2 files changed, 25 insertions(+), 1 deletion(-)
|
|
create mode 100644 include/linux/phy/phy-hdmi.h
|
|
|
|
diff --git a/include/linux/phy/phy-hdmi.h b/include/linux/phy/phy-hdmi.h
|
|
new file mode 100644
|
|
index 0000000000000..6a696922bc7f2
|
|
--- /dev/null
|
|
+++ b/include/linux/phy/phy-hdmi.h
|
|
@@ -0,0 +1,19 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0 */
|
|
+/*
|
|
+ * Copyright 2022,2024 NXP
|
|
+ */
|
|
+
|
|
+#ifndef __PHY_HDMI_H_
|
|
+#define __PHY_HDMI_H_
|
|
+
|
|
+/**
|
|
+ * struct phy_configure_opts_hdmi - HDMI configuration set
|
|
+ * @tmds_char_rate: HDMI TMDS Character Rate in Hertz.
|
|
+ *
|
|
+ * This structure is used to represent the configuration state of a HDMI phy.
|
|
+ */
|
|
+struct phy_configure_opts_hdmi {
|
|
+ unsigned long long tmds_char_rate;
|
|
+};
|
|
+
|
|
+#endif /* __PHY_HDMI_H_ */
|
|
diff --git a/include/linux/phy/phy.h b/include/linux/phy/phy.h
|
|
index 03cd5bae92d3f..4ac486b101fe4 100644
|
|
--- a/include/linux/phy/phy.h
|
|
+++ b/include/linux/phy/phy.h
|
|
@@ -17,6 +17,7 @@
|
|
#include <linux/regulator/consumer.h>
|
|
|
|
#include <linux/phy/phy-dp.h>
|
|
+#include <linux/phy/phy-hdmi.h>
|
|
#include <linux/phy/phy-lvds.h>
|
|
#include <linux/phy/phy-mipi-dphy.h>
|
|
|
|
@@ -42,7 +43,8 @@ enum phy_mode {
|
|
PHY_MODE_MIPI_DPHY,
|
|
PHY_MODE_SATA,
|
|
PHY_MODE_LVDS,
|
|
- PHY_MODE_DP
|
|
+ PHY_MODE_DP,
|
|
+ PHY_MODE_HDMI,
|
|
};
|
|
|
|
enum phy_media {
|
|
@@ -60,11 +62,14 @@ enum phy_media {
|
|
* the DisplayPort protocol.
|
|
* @lvds: Configuration set applicable for phys supporting
|
|
* the LVDS phy mode.
|
|
+ * @hdmi: Configuration set applicable for phys supporting
|
|
+ * the HDMI phy mode.
|
|
*/
|
|
union phy_configure_opts {
|
|
struct phy_configure_opts_mipi_dphy mipi_dphy;
|
|
struct phy_configure_opts_dp dp;
|
|
struct phy_configure_opts_lvds lvds;
|
|
+ struct phy_configure_opts_hdmi hdmi;
|
|
};
|
|
|
|
/**
|
|
|
|
From patchwork Tue Dec 17 06:51:46 2024
|
|
Content-Type: text/plain; charset="utf-8"
|
|
MIME-Version: 1.0
|
|
Content-Transfer-Encoding: 7bit
|
|
Subject: [v20,4/9] dt-bindings: display: bridge: Add Cadence MHDP8501
|
|
From: Sandor Yu <Sandor.yu@nxp.com>
|
|
X-Patchwork-Id: 629291
|
|
Message-Id:
|
|
<f2b7beebeb99ca69021ca24744e29af3454f5620.1734340233.git.Sandor.yu@nxp.com>
|
|
To: dmitry.baryshkov@linaro.org, andrzej.hajda@intel.com,
|
|
neil.armstrong@linaro.org, Laurent.pinchart@ideasonboard.com,
|
|
jonas@kwiboo.se, jernej.skrabec@gmail.com, airlied@gmail.com,
|
|
daniel@ffwll.ch, robh+dt@kernel.org, krzysztof.kozlowski+dt@linaro.org,
|
|
shawnguo@kernel.org, s.hauer@pengutronix.de, festevam@gmail.com,
|
|
vkoul@kernel.org, dri-devel@lists.freedesktop.org,
|
|
devicetree@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
|
|
linux-kernel@vger.kernel.org, linux-phy@lists.infradead.org,
|
|
mripard@kernel.org
|
|
Cc: kernel@pengutronix.de, linux-imx@nxp.com, Sandor.yu@nxp.com,
|
|
oliver.brown@nxp.com, alexander.stein@ew.tq-group.com, sam@ravnborg.org
|
|
Date: Tue, 17 Dec 2024 14:51:46 +0800
|
|
|
|
Add bindings for Cadence MHDP8501 DisplayPort/HDMI bridge.
|
|
|
|
Signed-off-by: Sandor Yu <Sandor.yu@nxp.com>
|
|
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
|
|
---
|
|
v19->v20:
|
|
- remove data type link of data-lanes.
|
|
|
|
v18->v19:
|
|
- move data-lanes property to endpoint of port@1
|
|
|
|
v17->v18:
|
|
- remove lane-mapping and replace it with data-lanes
|
|
- remove r-b tag as property changed.
|
|
|
|
v16->v17:
|
|
- Add lane-mapping property
|
|
|
|
v9->v16:
|
|
*No change
|
|
|
|
.../display/bridge/cdns,mhdp8501.yaml | 121 ++++++++++++++++++
|
|
1 file changed, 121 insertions(+)
|
|
create mode 100644 Documentation/devicetree/bindings/display/bridge/cdns,mhdp8501.yaml
|
|
|
|
diff --git a/Documentation/devicetree/bindings/display/bridge/cdns,mhdp8501.yaml b/Documentation/devicetree/bindings/display/bridge/cdns,mhdp8501.yaml
|
|
new file mode 100644
|
|
index 0000000000000..2417f4038b437
|
|
--- /dev/null
|
|
+++ b/Documentation/devicetree/bindings/display/bridge/cdns,mhdp8501.yaml
|
|
@@ -0,0 +1,121 @@
|
|
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
|
|
+%YAML 1.2
|
|
+---
|
|
+$id: http://devicetree.org/schemas/display/bridge/cdns,mhdp8501.yaml#
|
|
+$schema: http://devicetree.org/meta-schemas/core.yaml#
|
|
+
|
|
+title: Cadence MHDP8501 DP/HDMI bridge
|
|
+
|
|
+maintainers:
|
|
+ - Sandor Yu <Sandor.yu@nxp.com>
|
|
+
|
|
+description:
|
|
+ Cadence MHDP8501 DisplayPort/HDMI interface.
|
|
+
|
|
+properties:
|
|
+ compatible:
|
|
+ enum:
|
|
+ - fsl,imx8mq-mhdp8501
|
|
+
|
|
+ reg:
|
|
+ maxItems: 1
|
|
+
|
|
+ clocks:
|
|
+ maxItems: 1
|
|
+ description: MHDP8501 DP/HDMI APB clock.
|
|
+
|
|
+ phys:
|
|
+ maxItems: 1
|
|
+ description:
|
|
+ phandle to the DP/HDMI PHY
|
|
+
|
|
+ interrupts:
|
|
+ items:
|
|
+ - description: Hotplug cable plugin.
|
|
+ - description: Hotplug cable plugout.
|
|
+
|
|
+ interrupt-names:
|
|
+ items:
|
|
+ - const: plug_in
|
|
+ - const: plug_out
|
|
+
|
|
+ ports:
|
|
+ $ref: /schemas/graph.yaml#/properties/ports
|
|
+
|
|
+ properties:
|
|
+ port@0:
|
|
+ $ref: /schemas/graph.yaml#/properties/port
|
|
+ description:
|
|
+ Input port from display controller output.
|
|
+
|
|
+ port@1:
|
|
+ $ref: /schemas/graph.yaml#/$defs/port-base
|
|
+ unevaluatedProperties: false
|
|
+ description:
|
|
+ Output port to DisplayPort or HDMI connector.
|
|
+
|
|
+ properties:
|
|
+ endpoint:
|
|
+ $ref: /schemas/media/video-interfaces.yaml#
|
|
+ unevaluatedProperties: false
|
|
+
|
|
+ properties:
|
|
+ data-lanes:
|
|
+ description: Lane reordering for HDMI or DisplayPort interface.
|
|
+ minItems: 4
|
|
+ maxItems: 4
|
|
+
|
|
+ required:
|
|
+ - data-lanes
|
|
+
|
|
+ required:
|
|
+ - port@0
|
|
+ - port@1
|
|
+
|
|
+required:
|
|
+ - compatible
|
|
+ - reg
|
|
+ - clocks
|
|
+ - interrupts
|
|
+ - interrupt-names
|
|
+ - phys
|
|
+ - ports
|
|
+
|
|
+additionalProperties: false
|
|
+
|
|
+examples:
|
|
+ - |
|
|
+ #include <dt-bindings/clock/imx8mq-clock.h>
|
|
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
|
|
+
|
|
+ mhdp: display-bridge@32c00000 {
|
|
+ compatible = "fsl,imx8mq-mhdp8501";
|
|
+ reg = <0x32c00000 0x100000>;
|
|
+ interrupts = <GIC_SPI 16 IRQ_TYPE_LEVEL_HIGH>,
|
|
+ <GIC_SPI 25 IRQ_TYPE_LEVEL_HIGH>;
|
|
+ interrupt-names = "plug_in", "plug_out";
|
|
+ clocks = <&clk IMX8MQ_CLK_DISP_APB_ROOT>;
|
|
+ phys = <&mdhp_phy>;
|
|
+
|
|
+ ports {
|
|
+ #address-cells = <1>;
|
|
+ #size-cells = <0>;
|
|
+
|
|
+ port@0 {
|
|
+ reg = <0>;
|
|
+
|
|
+ mhdp_in: endpoint {
|
|
+ remote-endpoint = <&dcss_out>;
|
|
+ };
|
|
+ };
|
|
+
|
|
+ port@1 {
|
|
+ reg = <1>;
|
|
+
|
|
+ mhdp_out: endpoint {
|
|
+ remote-endpoint = <&dp_connector>;
|
|
+ data-lanes = <2 1 0 3>;
|
|
+ };
|
|
+ };
|
|
+ };
|
|
+ };
|
|
|
|
From patchwork Tue Dec 17 06:51:47 2024
|
|
Content-Type: text/plain; charset="utf-8"
|
|
MIME-Version: 1.0
|
|
Content-Transfer-Encoding: 7bit
|
|
Subject: [v20,5/9] drm: bridge: Cadence: Add MHDP8501 DP/HDMI driver
|
|
From: Sandor Yu <Sandor.yu@nxp.com>
|
|
X-Patchwork-Id: 629292
|
|
Message-Id:
|
|
<fbd12029fab1f60e2ba4fad75ef650298a59cb15.1734340233.git.Sandor.yu@nxp.com>
|
|
To: dmitry.baryshkov@linaro.org, andrzej.hajda@intel.com,
|
|
neil.armstrong@linaro.org, Laurent.pinchart@ideasonboard.com,
|
|
jonas@kwiboo.se, jernej.skrabec@gmail.com, airlied@gmail.com,
|
|
daniel@ffwll.ch, robh+dt@kernel.org, krzysztof.kozlowski+dt@linaro.org,
|
|
shawnguo@kernel.org, s.hauer@pengutronix.de, festevam@gmail.com,
|
|
vkoul@kernel.org, dri-devel@lists.freedesktop.org,
|
|
devicetree@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
|
|
linux-kernel@vger.kernel.org, linux-phy@lists.infradead.org,
|
|
mripard@kernel.org
|
|
Cc: kernel@pengutronix.de, linux-imx@nxp.com, Sandor.yu@nxp.com,
|
|
oliver.brown@nxp.com, alexander.stein@ew.tq-group.com, sam@ravnborg.org
|
|
Date: Tue, 17 Dec 2024 14:51:47 +0800
|
|
|
|
Add a new DRM DisplayPort and HDMI bridge driver for Candence MHDP8501
|
|
used in i.MX8MQ SOC. MHDP8501 could support HDMI or DisplayPort
|
|
standards according embedded Firmware running in the uCPU.
|
|
|
|
For iMX8MQ SOC, the DisplayPort/HDMI FW was loaded and activated by
|
|
SOC's ROM code. Bootload binary included respective specific firmware
|
|
is required.
|
|
|
|
Driver will check display connector type and
|
|
then load the corresponding driver.
|
|
|
|
Signed-off-by: Sandor Yu <Sandor.yu@nxp.com>
|
|
---
|
|
v19->v20:
|
|
- Dump mhdp FW version by debugfs
|
|
- Combine HDMI and DP cable detect functions into one function
|
|
- Combine HDMI and DP cable bridge_mode_valid() functions into one function
|
|
- Rename cdns_hdmi_reset_link() to cdns_hdmi_handle_hotplug()
|
|
- Add comments for EDID in cdns_hdmi_handle_hotplug() and cdns_dp_check_link_state()
|
|
- Add atomic_get_input_bus_fmts() and bridge_atomic_check() for DP driver
|
|
- Remove bpc and color_fmt init in atomic_enable() function.
|
|
- More detail comments for DDC adapter only support SCDC_I2C_SLAVE_ADDRESS
|
|
read and write in HDMI driver.
|
|
|
|
|
|
v18->v19:
|
|
- Get endpoint for data-lanes as it had move to endpoint of port@1
|
|
- Update clock management as devm_clk_get_enabled() introduced.
|
|
- Fix clear_infoframe() function is not work issue.
|
|
- Manage PHY power state via phy_power_on() and phy_power_off().
|
|
|
|
v17->v18:
|
|
- MHDP8501 HDMI and DP commands that need access mailbox are rewrited
|
|
with new API functions created in patch #1.
|
|
- replace lane-mapping with data-lanes, use the value from data-lanes
|
|
to reorder HDMI and DP lane mapping.
|
|
- create I2C adapter for HDMI SCDC, remove cdns_hdmi_scdc_write() function.
|
|
- Rewrite cdns_hdmi_sink_config() function, use HDMI SCDC helper function
|
|
drm_scdc_set_high_tmds_clock_ratio() and drm_scdc_set_scrambling()
|
|
to config HDMI sink TMDS.
|
|
- Remove struct video_info from HDMI driver.
|
|
- Remove tmds_char_rate_valid() be called in bridge_mode_valid(),
|
|
community had patch in reviewing to implement the function.
|
|
- Remove warning message print when get unknown HPD cable status.
|
|
- Add more detail comments for HDP plugin and plugout interrupt.
|
|
- use dev_dbg to repleace DRM_INFO when cable HPD status changed.
|
|
- Remove t-b tag as above code change.
|
|
|
|
drivers/gpu/drm/bridge/cadence/Kconfig | 16 +
|
|
drivers/gpu/drm/bridge/cadence/Makefile | 2 +
|
|
.../drm/bridge/cadence/cdns-mhdp8501-core.c | 379 +++++++++
|
|
.../drm/bridge/cadence/cdns-mhdp8501-core.h | 380 +++++++++
|
|
.../gpu/drm/bridge/cadence/cdns-mhdp8501-dp.c | 694 ++++++++++++++++
|
|
.../drm/bridge/cadence/cdns-mhdp8501-hdmi.c | 745 ++++++++++++++++++
|
|
6 files changed, 2216 insertions(+)
|
|
create mode 100644 drivers/gpu/drm/bridge/cadence/cdns-mhdp8501-core.c
|
|
create mode 100644 drivers/gpu/drm/bridge/cadence/cdns-mhdp8501-core.h
|
|
create mode 100644 drivers/gpu/drm/bridge/cadence/cdns-mhdp8501-dp.c
|
|
create mode 100644 drivers/gpu/drm/bridge/cadence/cdns-mhdp8501-hdmi.c
|
|
|
|
diff --git a/drivers/gpu/drm/bridge/cadence/Kconfig b/drivers/gpu/drm/bridge/cadence/Kconfig
|
|
index dbb06533ccab2..bd979f3e6df48 100644
|
|
--- a/drivers/gpu/drm/bridge/cadence/Kconfig
|
|
+++ b/drivers/gpu/drm/bridge/cadence/Kconfig
|
|
@@ -48,3 +48,19 @@ config DRM_CDNS_MHDP8546_J721E
|
|
initializes the J721E Display Port and sets up the
|
|
clock and data muxes.
|
|
endif
|
|
+
|
|
+config DRM_CDNS_MHDP8501
|
|
+ tristate "Cadence MHDP8501 DP/HDMI bridge"
|
|
+ select DRM_KMS_HELPER
|
|
+ select DRM_PANEL_BRIDGE
|
|
+ select DRM_DISPLAY_DP_HELPER
|
|
+ select DRM_DISPLAY_HELPER
|
|
+ select DRM_CDNS_AUDIO
|
|
+ select CDNS_MHDP_HELPER
|
|
+ depends on OF
|
|
+ help
|
|
+ Support Cadence MHDP8501 DisplayPort/HDMI bridge.
|
|
+ Cadence MHDP8501 support one or more protocols,
|
|
+ including DisplayPort and HDMI.
|
|
+ To use the DP and HDMI drivers, their respective
|
|
+ specific firmware is required.
|
|
diff --git a/drivers/gpu/drm/bridge/cadence/Makefile b/drivers/gpu/drm/bridge/cadence/Makefile
|
|
index c95fd5b81d137..ea327287d1c14 100644
|
|
--- a/drivers/gpu/drm/bridge/cadence/Makefile
|
|
+++ b/drivers/gpu/drm/bridge/cadence/Makefile
|
|
@@ -5,3 +5,5 @@ cdns-dsi-$(CONFIG_DRM_CDNS_DSI_J721E) += cdns-dsi-j721e.o
|
|
obj-$(CONFIG_DRM_CDNS_MHDP8546) += cdns-mhdp8546.o
|
|
cdns-mhdp8546-y := cdns-mhdp8546-core.o cdns-mhdp8546-hdcp.o
|
|
cdns-mhdp8546-$(CONFIG_DRM_CDNS_MHDP8546_J721E) += cdns-mhdp8546-j721e.o
|
|
+obj-$(CONFIG_DRM_CDNS_MHDP8501) += cdns-mhdp8501.o
|
|
+cdns-mhdp8501-y := cdns-mhdp8501-core.o cdns-mhdp8501-dp.o cdns-mhdp8501-hdmi.o
|
|
diff --git a/drivers/gpu/drm/bridge/cadence/cdns-mhdp8501-core.c b/drivers/gpu/drm/bridge/cadence/cdns-mhdp8501-core.c
|
|
new file mode 100644
|
|
index 0000000000000..98116ef012fa3
|
|
--- /dev/null
|
|
+++ b/drivers/gpu/drm/bridge/cadence/cdns-mhdp8501-core.c
|
|
@@ -0,0 +1,379 @@
|
|
+// SPDX-License-Identifier: GPL-2.0-only
|
|
+/*
|
|
+ * Cadence Display Port Interface (DP) driver
|
|
+ *
|
|
+ * Copyright (C) 2023-2024 NXP Semiconductor, Inc.
|
|
+ *
|
|
+ */
|
|
+#include <drm/drm_of.h>
|
|
+#include <drm/drm_print.h>
|
|
+#include <linux/clk.h>
|
|
+#include <linux/irq.h>
|
|
+#include <linux/mutex.h>
|
|
+#include <linux/of_device.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/phy/phy.h>
|
|
+
|
|
+#include "cdns-mhdp8501-core.h"
|
|
+
|
|
+static ssize_t firmware_version_show(struct device *dev,
|
|
+ struct device_attribute *attr, char *buf);
|
|
+static struct device_attribute firmware_version = __ATTR_RO(firmware_version);
|
|
+
|
|
+ssize_t firmware_version_show(struct device *dev,
|
|
+ struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ struct cdns_mhdp8501_device *mhdp = dev_get_drvdata(dev);
|
|
+
|
|
+ u32 version = readl(mhdp->base.regs + VER_L) | readl(mhdp->base.regs + VER_H) << 8;
|
|
+ u32 lib_version = readl(mhdp->base.regs + VER_LIB_L_ADDR) |
|
|
+ readl(mhdp->base.regs + VER_LIB_H_ADDR) << 8;
|
|
+
|
|
+ return sprintf(buf, "FW version %d, Lib version %d\n", version, lib_version);
|
|
+}
|
|
+
|
|
+static void cdns_mhdp8501_create_device_files(struct cdns_mhdp8501_device *mhdp)
|
|
+{
|
|
+ if (device_create_file(mhdp->dev, &firmware_version)) {
|
|
+ DRM_ERROR("Unable to create firmware_version sysfs\n");
|
|
+ device_remove_file(mhdp->dev, &firmware_version);
|
|
+ }
|
|
+}
|
|
+
|
|
+static void cdns_mhdp8501_remove_device_files(struct cdns_mhdp8501_device *mhdp)
|
|
+{
|
|
+ device_remove_file(mhdp->dev, &firmware_version);
|
|
+}
|
|
+
|
|
+static int cdns_mhdp8501_read_hpd(struct cdns_mhdp8501_device *mhdp)
|
|
+{
|
|
+ u8 status;
|
|
+ int ret;
|
|
+
|
|
+ ret = cdns_mhdp_mailbox_send_recv(&mhdp->base, MB_MODULE_ID_GENERAL,
|
|
+ GENERAL_GET_HPD_STATE,
|
|
+ 0, NULL, sizeof(status), &status);
|
|
+ if (ret) {
|
|
+ dev_err(mhdp->dev, "read hpd failed: %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ return status;
|
|
+}
|
|
+
|
|
+enum drm_connector_status cdns_mhdp8501_detect(struct drm_bridge *bridge)
|
|
+{
|
|
+ struct cdns_mhdp8501_device *mhdp = bridge->driver_private;
|
|
+
|
|
+ u8 hpd = 0xf;
|
|
+
|
|
+ hpd = cdns_mhdp8501_read_hpd(mhdp);
|
|
+ if (hpd == 1)
|
|
+ return connector_status_connected;
|
|
+ else if (hpd == 0)
|
|
+ return connector_status_disconnected;
|
|
+
|
|
+ return connector_status_unknown;
|
|
+}
|
|
+
|
|
+enum drm_mode_status
|
|
+cdns_mhdp8501_mode_valid(struct drm_bridge *bridge,
|
|
+ const struct drm_display_info *info,
|
|
+ const struct drm_display_mode *mode)
|
|
+{
|
|
+ /* We don't support double-clocked */
|
|
+ if (mode->flags & DRM_MODE_FLAG_DBLCLK)
|
|
+ return MODE_BAD;
|
|
+
|
|
+ /* MAX support pixel clock rate 594MHz */
|
|
+ if (mode->clock > 594000)
|
|
+ return MODE_CLOCK_HIGH;
|
|
+
|
|
+ if (mode->hdisplay > 3840)
|
|
+ return MODE_BAD_HVALUE;
|
|
+
|
|
+ if (mode->vdisplay > 2160)
|
|
+ return MODE_BAD_VVALUE;
|
|
+
|
|
+ return MODE_OK;
|
|
+}
|
|
+
|
|
+static void hotplug_work_func(struct work_struct *work)
|
|
+{
|
|
+ struct cdns_mhdp8501_device *mhdp = container_of(work,
|
|
+ struct cdns_mhdp8501_device,
|
|
+ hotplug_work.work);
|
|
+ enum drm_connector_status status = cdns_mhdp8501_detect(&mhdp->bridge);
|
|
+
|
|
+ drm_bridge_hpd_notify(&mhdp->bridge, status);
|
|
+
|
|
+ /*
|
|
+ * iMX8MQ has two HPD interrupts: one for plugout and one for plugin.
|
|
+ * These interrupts cannot be masked and cleaned, so we must enable one
|
|
+ * and disable the other to avoid continuous interrupt generation.
|
|
+ */
|
|
+ if (status == connector_status_connected) {
|
|
+ /* Cable connected */
|
|
+ dev_dbg(mhdp->dev, "HDMI/DP Cable Plug In\n");
|
|
+ enable_irq(mhdp->irq[IRQ_OUT]);
|
|
+
|
|
+ /* Reset HDMI/DP link with sink */
|
|
+ if (mhdp->connector_type == DRM_MODE_CONNECTOR_HDMIA)
|
|
+ cdns_hdmi_handle_hotplug(mhdp);
|
|
+ else
|
|
+ cdns_dp_check_link_state(mhdp);
|
|
+
|
|
+ } else if (status == connector_status_disconnected) {
|
|
+ /* Cable Disconnected */
|
|
+ dev_dbg(mhdp->dev, "HDMI/DP Cable Plug Out\n");
|
|
+ enable_irq(mhdp->irq[IRQ_IN]);
|
|
+ }
|
|
+}
|
|
+
|
|
+static irqreturn_t cdns_mhdp8501_irq_thread(int irq, void *data)
|
|
+{
|
|
+ struct cdns_mhdp8501_device *mhdp = data;
|
|
+
|
|
+ disable_irq_nosync(irq);
|
|
+
|
|
+ mod_delayed_work(system_wq, &mhdp->hotplug_work,
|
|
+ msecs_to_jiffies(HOTPLUG_DEBOUNCE_MS));
|
|
+
|
|
+ return IRQ_HANDLED;
|
|
+}
|
|
+
|
|
+#define DATA_LANES_COUNT 4
|
|
+static int cdns_mhdp8501_dt_parse(struct cdns_mhdp8501_device *mhdp,
|
|
+ struct platform_device *pdev)
|
|
+{
|
|
+ struct device *dev = &pdev->dev;
|
|
+ struct device_node *np = dev->of_node;
|
|
+ struct device_node *remote, *endpoint;
|
|
+ u32 data_lanes[DATA_LANES_COUNT];
|
|
+ u32 lane_value;
|
|
+ int ret, i;
|
|
+
|
|
+ remote = of_graph_get_remote_node(np, 1, 0);
|
|
+ if (!remote) {
|
|
+ dev_err(dev, "fail to get remote node\n");
|
|
+ of_node_put(remote);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ /* get connector type */
|
|
+ if (of_device_is_compatible(remote, "hdmi-connector")) {
|
|
+ mhdp->connector_type = DRM_MODE_CONNECTOR_HDMIA;
|
|
+
|
|
+ } else if (of_device_is_compatible(remote, "dp-connector")) {
|
|
+ mhdp->connector_type = DRM_MODE_CONNECTOR_DisplayPort;
|
|
+
|
|
+ } else {
|
|
+ dev_err(dev, "Unknown connector type\n");
|
|
+ of_node_put(remote);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ of_node_put(remote);
|
|
+
|
|
+ endpoint = of_graph_get_endpoint_by_regs(np, 1, -1);
|
|
+
|
|
+ /* Get the data lanes ordering */
|
|
+ ret = of_property_count_u32_elems(endpoint, "data-lanes");
|
|
+ if (ret < 0)
|
|
+ return -EINVAL;
|
|
+ if (ret != DATA_LANES_COUNT) {
|
|
+ dev_err(dev, "expected 4 data lanes\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ ret = of_property_read_u32_array(endpoint, "data-lanes",
|
|
+ data_lanes, DATA_LANES_COUNT);
|
|
+ if (ret)
|
|
+ return -EINVAL;
|
|
+
|
|
+ mhdp->lane_mapping = 0;
|
|
+ for (i = 0; i < DATA_LANES_COUNT; i++) {
|
|
+ lane_value = (data_lanes[i] >= 0 && data_lanes[i] <= 3) ? data_lanes[i] : 0;
|
|
+ mhdp->lane_mapping |= lane_value << (i * 2);
|
|
+ }
|
|
+
|
|
+ return true;
|
|
+}
|
|
+
|
|
+static int cdns_mhdp8501_add_bridge(struct cdns_mhdp8501_device *mhdp)
|
|
+{
|
|
+ mhdp->bridge.type = mhdp->connector_type;
|
|
+ mhdp->bridge.driver_private = mhdp;
|
|
+ mhdp->bridge.of_node = mhdp->dev->of_node;
|
|
+ mhdp->bridge.vendor = "NXP";
|
|
+ mhdp->bridge.product = "i.MX8";
|
|
+ mhdp->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID |
|
|
+ DRM_BRIDGE_OP_HPD;
|
|
+
|
|
+ if (mhdp->connector_type == DRM_MODE_CONNECTOR_HDMIA) {
|
|
+ mhdp->bridge.funcs = &cdns_hdmi_bridge_funcs;
|
|
+ mhdp->bridge.ops |= DRM_BRIDGE_OP_HDMI;
|
|
+ mhdp->bridge.ddc = cdns_hdmi_i2c_adapter(mhdp);
|
|
+ } else if (mhdp->connector_type == DRM_MODE_CONNECTOR_DisplayPort) {
|
|
+ mhdp->bridge.funcs = &cdns_dp_bridge_funcs;
|
|
+ } else {
|
|
+ dev_err(mhdp->dev, "Unsupported connector type!\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ drm_bridge_add(&mhdp->bridge);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int cdns_mhdp8501_probe(struct platform_device *pdev)
|
|
+{
|
|
+ struct device *dev = &pdev->dev;
|
|
+ struct cdns_mhdp8501_device *mhdp;
|
|
+ struct resource *res;
|
|
+ enum phy_mode phy_mode;
|
|
+ u32 reg;
|
|
+ int ret;
|
|
+
|
|
+ mhdp = devm_kzalloc(dev, sizeof(*mhdp), GFP_KERNEL);
|
|
+ if (!mhdp)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ mhdp->dev = dev;
|
|
+
|
|
+ INIT_DELAYED_WORK(&mhdp->hotplug_work, hotplug_work_func);
|
|
+
|
|
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
+ if (!res)
|
|
+ return -ENODEV;
|
|
+
|
|
+ mhdp->regs = devm_ioremap(dev, res->start, resource_size(res));
|
|
+ if (IS_ERR(mhdp->regs))
|
|
+ return PTR_ERR(mhdp->regs);
|
|
+
|
|
+ cdns_mhdp8501_create_device_files(mhdp);
|
|
+
|
|
+ ret = cdns_mhdp8501_dt_parse(mhdp, pdev);
|
|
+ if (ret < 0)
|
|
+ return -EINVAL;
|
|
+
|
|
+ mhdp->phy = devm_of_phy_get_by_index(dev, pdev->dev.of_node, 0);
|
|
+ if (IS_ERR(mhdp->phy))
|
|
+ return dev_err_probe(dev, PTR_ERR(mhdp->phy), "no PHY configured\n");
|
|
+
|
|
+ mhdp->irq[IRQ_IN] = platform_get_irq_byname(pdev, "plug_in");
|
|
+ if (mhdp->irq[IRQ_IN] < 0)
|
|
+ return dev_err_probe(dev, mhdp->irq[IRQ_IN], "No plug_in irq number\n");
|
|
+
|
|
+ mhdp->irq[IRQ_OUT] = platform_get_irq_byname(pdev, "plug_out");
|
|
+ if (mhdp->irq[IRQ_OUT] < 0)
|
|
+ return dev_err_probe(dev, mhdp->irq[IRQ_OUT], "No plug_out irq number\n");
|
|
+
|
|
+ irq_set_status_flags(mhdp->irq[IRQ_IN], IRQ_NOAUTOEN);
|
|
+ ret = devm_request_threaded_irq(dev, mhdp->irq[IRQ_IN],
|
|
+ NULL, cdns_mhdp8501_irq_thread,
|
|
+ IRQF_ONESHOT, dev_name(dev), mhdp);
|
|
+ if (ret < 0) {
|
|
+ dev_err(dev, "can't claim irq %d\n", mhdp->irq[IRQ_IN]);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ irq_set_status_flags(mhdp->irq[IRQ_OUT], IRQ_NOAUTOEN);
|
|
+ ret = devm_request_threaded_irq(dev, mhdp->irq[IRQ_OUT],
|
|
+ NULL, cdns_mhdp8501_irq_thread,
|
|
+ IRQF_ONESHOT, dev_name(dev), mhdp);
|
|
+ if (ret < 0) {
|
|
+ dev_err(dev, "can't claim irq %d\n", mhdp->irq[IRQ_OUT]);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ /* cdns_mhdp8501_dt_parse() ensures connector_type is valid */
|
|
+ if (mhdp->connector_type == DRM_MODE_CONNECTOR_DisplayPort)
|
|
+ phy_mode = PHY_MODE_DP;
|
|
+ else if (mhdp->connector_type == DRM_MODE_CONNECTOR_HDMIA)
|
|
+ phy_mode = PHY_MODE_HDMI;
|
|
+
|
|
+ dev_set_drvdata(dev, mhdp);
|
|
+
|
|
+ /* init base struct for access mhdp mailbox */
|
|
+ mhdp->base.dev = mhdp->dev;
|
|
+ mhdp->base.regs = mhdp->regs;
|
|
+
|
|
+ if (mhdp->connector_type == DRM_MODE_CONNECTOR_DisplayPort) {
|
|
+ drm_dp_aux_init(&mhdp->dp.aux);
|
|
+ mhdp->dp.aux.name = "mhdp8501_dp_aux";
|
|
+ mhdp->dp.aux.dev = dev;
|
|
+ mhdp->dp.aux.transfer = cdns_dp_aux_transfer;
|
|
+ }
|
|
+
|
|
+ /* Enable APB clock */
|
|
+ mhdp->apb_clk = devm_clk_get_enabled(dev, NULL);
|
|
+ if (IS_ERR(mhdp->apb_clk))
|
|
+ return dev_err_probe(dev, PTR_ERR(mhdp->apb_clk),
|
|
+ "couldn't get apb clk\n");
|
|
+ /*
|
|
+ * Wait for the KEEP_ALIVE "message" on the first 8 bits.
|
|
+ * Updated each sched "tick" (~2ms)
|
|
+ */
|
|
+ ret = readl_poll_timeout(mhdp->regs + KEEP_ALIVE, reg,
|
|
+ reg & CDNS_KEEP_ALIVE_MASK, 500,
|
|
+ CDNS_KEEP_ALIVE_TIMEOUT);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "device didn't give any life sign: reg %d\n", reg);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = phy_init(mhdp->phy);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "Failed to initialize PHY: %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = phy_set_mode(mhdp->phy, phy_mode);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "Failed to configure PHY: %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ /* Enable cable hotplug detect */
|
|
+ if (cdns_mhdp8501_read_hpd(mhdp))
|
|
+ enable_irq(mhdp->irq[IRQ_OUT]);
|
|
+ else
|
|
+ enable_irq(mhdp->irq[IRQ_IN]);
|
|
+
|
|
+ return cdns_mhdp8501_add_bridge(mhdp);
|
|
+}
|
|
+
|
|
+static void cdns_mhdp8501_remove(struct platform_device *pdev)
|
|
+{
|
|
+ struct cdns_mhdp8501_device *mhdp = platform_get_drvdata(pdev);
|
|
+
|
|
+ cdns_mhdp8501_remove_device_files(mhdp);
|
|
+
|
|
+ if (mhdp->connector_type == DRM_MODE_CONNECTOR_DisplayPort)
|
|
+ cdns_dp_aux_destroy(mhdp);
|
|
+
|
|
+ drm_bridge_remove(&mhdp->bridge);
|
|
+}
|
|
+
|
|
+static const struct of_device_id cdns_mhdp8501_dt_ids[] = {
|
|
+ { .compatible = "fsl,imx8mq-mhdp8501",
|
|
+ },
|
|
+ { },
|
|
+};
|
|
+MODULE_DEVICE_TABLE(of, cdns_mhdp8501_dt_ids);
|
|
+
|
|
+static struct platform_driver cdns_mhdp8501_driver = {
|
|
+ .probe = cdns_mhdp8501_probe,
|
|
+ .remove = cdns_mhdp8501_remove,
|
|
+ .driver = {
|
|
+ .name = "cdns-mhdp8501",
|
|
+ .of_match_table = cdns_mhdp8501_dt_ids,
|
|
+ },
|
|
+};
|
|
+
|
|
+module_platform_driver(cdns_mhdp8501_driver);
|
|
+
|
|
+MODULE_AUTHOR("Sandor Yu <sandor.yu@nxp.com>");
|
|
+MODULE_DESCRIPTION("Cadence MHDP8501 bridge driver");
|
|
+MODULE_LICENSE("GPL");
|
|
diff --git a/drivers/gpu/drm/bridge/cadence/cdns-mhdp8501-core.h b/drivers/gpu/drm/bridge/cadence/cdns-mhdp8501-core.h
|
|
new file mode 100644
|
|
index 0000000000000..8fc463098ab84
|
|
--- /dev/null
|
|
+++ b/drivers/gpu/drm/bridge/cadence/cdns-mhdp8501-core.h
|
|
@@ -0,0 +1,380 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0 */
|
|
+/*
|
|
+ * Cadence MHDP 8501 Common head file
|
|
+ *
|
|
+ * Copyright (C) 2019-2024 NXP Semiconductor, Inc.
|
|
+ *
|
|
+ */
|
|
+
|
|
+#ifndef _CDNS_MHDP8501_CORE_H_
|
|
+#define _CDNS_MHDP8501_CORE_H_
|
|
+
|
|
+#include <drm/drm_bridge.h>
|
|
+#include <drm/drm_connector.h>
|
|
+#include <drm/display/drm_dp_helper.h>
|
|
+#include <linux/bitops.h>
|
|
+#include <linux/i2c.h>
|
|
+#include <soc/cadence/cdns-mhdp-helper.h>
|
|
+
|
|
+#define ADDR_IMEM 0x10000
|
|
+#define ADDR_DMEM 0x20000
|
|
+
|
|
+/* APB CFG addr */
|
|
+#define APB_CTRL 0
|
|
+#define XT_INT_CTRL 0x04
|
|
+#define MAILBOX_FULL_ADDR 0x08
|
|
+#define MAILBOX_EMPTY_ADDR 0x0c
|
|
+#define MAILBOX0_WR_DATA 0x10
|
|
+#define MAILBOX0_RD_DATA 0x14
|
|
+#define KEEP_ALIVE 0x18
|
|
+#define VER_L 0x1c
|
|
+#define VER_H 0x20
|
|
+#define VER_LIB_L_ADDR 0x24
|
|
+#define VER_LIB_H_ADDR 0x28
|
|
+#define SW_DEBUG_L 0x2c
|
|
+#define SW_DEBUG_H 0x30
|
|
+#define MAILBOX_INT_MASK 0x34
|
|
+#define MAILBOX_INT_STATUS 0x38
|
|
+#define SW_CLK_L 0x3c
|
|
+#define SW_CLK_H 0x40
|
|
+#define SW_EVENTS0 0x44
|
|
+#define SW_EVENTS1 0x48
|
|
+#define SW_EVENTS2 0x4c
|
|
+#define SW_EVENTS3 0x50
|
|
+#define XT_OCD_CTRL 0x60
|
|
+#define APB_INT_MASK 0x6c
|
|
+#define APB_STATUS_MASK 0x70
|
|
+
|
|
+/* Source phy comp */
|
|
+#define PHY_DATA_SEL 0x0818
|
|
+#define LANES_CONFIG 0x0814
|
|
+
|
|
+/* Source CAR Addr */
|
|
+#define SOURCE_HDTX_CAR 0x0900
|
|
+#define SOURCE_DPTX_CAR 0x0904
|
|
+#define SOURCE_PHY_CAR 0x0908
|
|
+#define SOURCE_CEC_CAR 0x090c
|
|
+#define SOURCE_CBUS_CAR 0x0910
|
|
+#define SOURCE_PKT_CAR 0x0918
|
|
+#define SOURCE_AIF_CAR 0x091c
|
|
+#define SOURCE_CIPHER_CAR 0x0920
|
|
+#define SOURCE_CRYPTO_CAR 0x0924
|
|
+
|
|
+/* clock meters addr */
|
|
+#define CM_CTRL 0x0a00
|
|
+#define CM_I2S_CTRL 0x0a04
|
|
+#define CM_SPDIF_CTRL 0x0a08
|
|
+#define CM_VID_CTRL 0x0a0c
|
|
+#define CM_LANE_CTRL 0x0a10
|
|
+#define I2S_NM_STABLE 0x0a14
|
|
+#define I2S_NCTS_STABLE 0x0a18
|
|
+#define SPDIF_NM_STABLE 0x0a1c
|
|
+#define SPDIF_NCTS_STABLE 0x0a20
|
|
+#define NMVID_MEAS_STABLE 0x0a24
|
|
+#define I2S_MEAS 0x0a40
|
|
+#define SPDIF_MEAS 0x0a80
|
|
+#define NMVID_MEAS 0x0ac0
|
|
+
|
|
+/* source vif addr */
|
|
+#define BND_HSYNC2VSYNC 0x0b00
|
|
+#define HSYNC2VSYNC_F1_L1 0x0b04
|
|
+#define HSYNC2VSYNC_STATUS 0x0b0c
|
|
+#define HSYNC2VSYNC_POL_CTRL 0x0b10
|
|
+
|
|
+/* MHDP TX_top_comp */
|
|
+#define SCHEDULER_H_SIZE 0x1000
|
|
+#define SCHEDULER_V_SIZE 0x1004
|
|
+#define HDTX_SIGNAL_FRONT_WIDTH 0x100c
|
|
+#define HDTX_SIGNAL_SYNC_WIDTH 0x1010
|
|
+#define HDTX_SIGNAL_BACK_WIDTH 0x1014
|
|
+#define HDTX_CONTROLLER 0x1018
|
|
+#define HDTX_HPD 0x1020
|
|
+#define HDTX_CLOCK_REG_0 0x1024
|
|
+#define HDTX_CLOCK_REG_1 0x1028
|
|
+
|
|
+/* DPTX hpd addr */
|
|
+#define HPD_IRQ_DET_MIN_TIMER 0x2100
|
|
+#define HPD_IRQ_DET_MAX_TIMER 0x2104
|
|
+#define HPD_UNPLGED_DET_MIN_TIMER 0x2108
|
|
+#define HPD_STABLE_TIMER 0x210c
|
|
+#define HPD_FILTER_TIMER 0x2110
|
|
+#define HPD_EVENT_MASK 0x211c
|
|
+#define HPD_EVENT_DET 0x2120
|
|
+
|
|
+/* DPTX framer addr */
|
|
+#define DP_FRAMER_GLOBAL_CONFIG 0x2200
|
|
+#define DP_SW_RESET 0x2204
|
|
+#define DP_FRAMER_TU 0x2208
|
|
+#define DP_FRAMER_PXL_REPR 0x220c
|
|
+#define DP_FRAMER_SP 0x2210
|
|
+#define AUDIO_PACK_CONTROL 0x2214
|
|
+#define DP_VC_TABLE(x) (0x2218 + ((x) << 2))
|
|
+#define DP_VB_ID 0x2258
|
|
+#define DP_MTPH_LVP_CONTROL 0x225c
|
|
+#define DP_MTPH_SYMBOL_VALUES 0x2260
|
|
+#define DP_MTPH_ECF_CONTROL 0x2264
|
|
+#define DP_MTPH_ACT_CONTROL 0x2268
|
|
+#define DP_MTPH_STATUS 0x226c
|
|
+#define DP_INTERRUPT_SOURCE 0x2270
|
|
+#define DP_INTERRUPT_MASK 0x2274
|
|
+#define DP_FRONT_BACK_PORCH 0x2278
|
|
+#define DP_BYTE_COUNT 0x227c
|
|
+
|
|
+/* DPTX stream addr */
|
|
+#define MSA_HORIZONTAL_0 0x2280
|
|
+#define MSA_HORIZONTAL_1 0x2284
|
|
+#define MSA_VERTICAL_0 0x2288
|
|
+#define MSA_VERTICAL_1 0x228c
|
|
+#define MSA_MISC 0x2290
|
|
+#define STREAM_CONFIG 0x2294
|
|
+#define AUDIO_PACK_STATUS 0x2298
|
|
+#define VIF_STATUS 0x229c
|
|
+#define PCK_STUFF_STATUS_0 0x22a0
|
|
+#define PCK_STUFF_STATUS_1 0x22a4
|
|
+#define INFO_PACK_STATUS 0x22a8
|
|
+#define RATE_GOVERNOR_STATUS 0x22ac
|
|
+#define DP_HORIZONTAL 0x22b0
|
|
+#define DP_VERTICAL_0 0x22b4
|
|
+#define DP_VERTICAL_1 0x22b8
|
|
+#define DP_BLOCK_SDP 0x22bc
|
|
+
|
|
+/* DPTX glbl addr */
|
|
+#define DPTX_LANE_EN 0x2300
|
|
+#define DPTX_ENHNCD 0x2304
|
|
+#define DPTX_INT_MASK 0x2308
|
|
+#define DPTX_INT_STATUS 0x230c
|
|
+
|
|
+/* DP AUX Addr */
|
|
+#define DP_AUX_HOST_CONTROL 0x2800
|
|
+#define DP_AUX_INTERRUPT_SOURCE 0x2804
|
|
+#define DP_AUX_INTERRUPT_MASK 0x2808
|
|
+#define DP_AUX_SWAP_INVERSION_CONTROL 0x280c
|
|
+#define DP_AUX_SEND_NACK_TRANSACTION 0x2810
|
|
+#define DP_AUX_CLEAR_RX 0x2814
|
|
+#define DP_AUX_CLEAR_TX 0x2818
|
|
+#define DP_AUX_TIMER_STOP 0x281c
|
|
+#define DP_AUX_TIMER_CLEAR 0x2820
|
|
+#define DP_AUX_RESET_SW 0x2824
|
|
+#define DP_AUX_DIVIDE_2M 0x2828
|
|
+#define DP_AUX_TX_PREACHARGE_LENGTH 0x282c
|
|
+#define DP_AUX_FREQUENCY_1M_MAX 0x2830
|
|
+#define DP_AUX_FREQUENCY_1M_MIN 0x2834
|
|
+#define DP_AUX_RX_PRE_MIN 0x2838
|
|
+#define DP_AUX_RX_PRE_MAX 0x283c
|
|
+#define DP_AUX_TIMER_PRESET 0x2840
|
|
+#define DP_AUX_NACK_FORMAT 0x2844
|
|
+#define DP_AUX_TX_DATA 0x2848
|
|
+#define DP_AUX_RX_DATA 0x284c
|
|
+#define DP_AUX_TX_STATUS 0x2850
|
|
+#define DP_AUX_RX_STATUS 0x2854
|
|
+#define DP_AUX_RX_CYCLE_COUNTER 0x2858
|
|
+#define DP_AUX_MAIN_STATES 0x285c
|
|
+#define DP_AUX_MAIN_TIMER 0x2860
|
|
+#define DP_AUX_AFE_OUT 0x2864
|
|
+
|
|
+/* source pif addr */
|
|
+#define SOURCE_PIF_WR_ADDR 0x30800
|
|
+#define SOURCE_PIF_WR_REQ 0x30804
|
|
+#define SOURCE_PIF_RD_ADDR 0x30808
|
|
+#define SOURCE_PIF_RD_REQ 0x3080c
|
|
+#define SOURCE_PIF_DATA_WR 0x30810
|
|
+#define SOURCE_PIF_DATA_RD 0x30814
|
|
+#define SOURCE_PIF_FIFO1_FLUSH 0x30818
|
|
+#define SOURCE_PIF_FIFO2_FLUSH 0x3081c
|
|
+#define SOURCE_PIF_STATUS 0x30820
|
|
+#define SOURCE_PIF_INTERRUPT_SOURCE 0x30824
|
|
+#define SOURCE_PIF_INTERRUPT_MASK 0x30828
|
|
+#define SOURCE_PIF_PKT_ALLOC_REG 0x3082c
|
|
+#define SOURCE_PIF_PKT_ALLOC_WR_EN 0x30830
|
|
+#define SOURCE_PIF_SW_RESET 0x30834
|
|
+
|
|
+#define LINK_TRAINING_NOT_ACTIV 0
|
|
+#define LINK_TRAINING_RUN 1
|
|
+#define LINK_TRAINING_RESTART 2
|
|
+
|
|
+#define CONTROL_VIDEO_IDLE 0
|
|
+#define CONTROL_VIDEO_VALID 1
|
|
+
|
|
+#define INTERLACE_FMT_DET BIT(12)
|
|
+#define VIF_BYPASS_INTERLACE BIT(13)
|
|
+#define TU_CNT_RST_EN BIT(15)
|
|
+#define INTERLACE_DTCT_WIN 0x20
|
|
+
|
|
+#define DP_FRAMER_SP_INTERLACE_EN BIT(2)
|
|
+#define DP_FRAMER_SP_HSP BIT(1)
|
|
+#define DP_FRAMER_SP_VSP BIT(0)
|
|
+
|
|
+/* Capability */
|
|
+#define AUX_HOST_INVERT 3
|
|
+#define FAST_LT_SUPPORT 1
|
|
+#define FAST_LT_NOT_SUPPORT 0
|
|
+#define LANE_MAPPING_FLIPPED 0xe4
|
|
+#define ENHANCED 1
|
|
+#define SCRAMBLER_EN BIT(4)
|
|
+
|
|
+#define FULL_LT_STARTED BIT(0)
|
|
+#define FASE_LT_STARTED BIT(1)
|
|
+#define CLK_RECOVERY_FINISHED BIT(2)
|
|
+#define EQ_PHASE_FINISHED BIT(3)
|
|
+#define FASE_LT_START_FINISHED BIT(4)
|
|
+#define CLK_RECOVERY_FAILED BIT(5)
|
|
+#define EQ_PHASE_FAILED BIT(6)
|
|
+#define FASE_LT_FAILED BIT(7)
|
|
+
|
|
+#define TU_SIZE 30
|
|
+#define CDNS_DP_MAX_LINK_RATE 540000
|
|
+
|
|
+#define F_HDMI2_CTRL_IL_MODE(x) (((x) & ((1 << 1) - 1)) << 19)
|
|
+#define F_HDMI2_PREAMBLE_EN(x) (((x) & ((1 << 1) - 1)) << 18)
|
|
+#define F_HDMI_ENCODING(x) (((x) & ((1 << 2) - 1)) << 16)
|
|
+#define F_DATA_EN(x) (((x) & ((1 << 1) - 1)) << 15)
|
|
+#define F_CLEAR_AVMUTE(x) (((x) & ((1 << 1) - 1)) << 14)
|
|
+#define F_SET_AVMUTE(x) (((x) & ((1 << 1) - 1)) << 13)
|
|
+#define F_GCP_EN(x) (((x) & ((1 << 1) - 1)) << 12)
|
|
+#define F_BCH_EN(x) (((x) & ((1 << 1) - 1)) << 11)
|
|
+#define F_PIC_3D(x) (((x) & ((1 << 4) - 1)) << 7)
|
|
+#define F_VIF_DATA_WIDTH(x) (((x) & ((1 << 2) - 1)) << 2)
|
|
+#define F_HDMI_MODE(x) (((x) & ((1 << 2) - 1)) << 0)
|
|
+
|
|
+#define F_SOURCE_PHY_MHDP_SEL(x) (((x) & ((1 << 2) - 1)) << 3)
|
|
+
|
|
+#define F_HPD_GLITCH_WIDTH(x) (((x) & ((1 << 8) - 1)) << 12)
|
|
+#define F_PACKET_TYPE(x) (((x) & ((1 << 8) - 1)) << 8)
|
|
+#define F_HPD_VALID_WIDTH(x) (((x) & ((1 << 12) - 1)) << 0)
|
|
+
|
|
+#define F_SOURCE_PHY_LANE3_SWAP(x) (((x) & ((1 << 2) - 1)) << 6)
|
|
+#define F_SOURCE_PHY_LANE2_SWAP(x) (((x) & ((1 << 2) - 1)) << 4)
|
|
+#define F_SOURCE_PHY_LANE1_SWAP(x) (((x) & ((1 << 2) - 1)) << 2)
|
|
+#define F_SOURCE_PHY_LANE0_SWAP(x) (((x) & ((1 << 2) - 1)) << 0)
|
|
+
|
|
+#define F_ACTIVE_IDLE_TYPE(x) (((x) & ((1 << 1) - 1)) << 17)
|
|
+#define F_TYPE_VALID(x) (((x) & ((1 << 1) - 1)) << 16)
|
|
+#define F_PKT_ALLOC_ADDRESS(x) (((x) & ((1 << 4) - 1)) << 0)
|
|
+
|
|
+#define F_FIFO1_FLUSH(x) (((x) & ((1 << 1) - 1)) << 0)
|
|
+#define F_PKT_ALLOC_WR_EN(x) (((x) & ((1 << 1) - 1)) << 0)
|
|
+#define F_DATA_WR(x) (x)
|
|
+#define F_WR_ADDR(x) (((x) & ((1 << 4) - 1)) << 0)
|
|
+#define F_HOST_WR(x) (((x) & ((1 << 1) - 1)) << 0)
|
|
+
|
|
+/* Reference cycles when using lane clock as reference */
|
|
+#define LANE_REF_CYC 0x8000
|
|
+
|
|
+/* HPD Debounce */
|
|
+#define HOTPLUG_DEBOUNCE_MS 200
|
|
+
|
|
+/* HPD IRQ Index */
|
|
+#define IRQ_IN 0
|
|
+#define IRQ_OUT 1
|
|
+#define IRQ_NUM 2
|
|
+
|
|
+/* FW check alive timeout */
|
|
+#define CDNS_KEEP_ALIVE_TIMEOUT 2000
|
|
+#define CDNS_KEEP_ALIVE_MASK GENMASK(7, 0)
|
|
+
|
|
+enum voltage_swing_level {
|
|
+ VOLTAGE_LEVEL_0,
|
|
+ VOLTAGE_LEVEL_1,
|
|
+ VOLTAGE_LEVEL_2,
|
|
+ VOLTAGE_LEVEL_3,
|
|
+};
|
|
+
|
|
+enum pre_emphasis_level {
|
|
+ PRE_EMPHASIS_LEVEL_0,
|
|
+ PRE_EMPHASIS_LEVEL_1,
|
|
+ PRE_EMPHASIS_LEVEL_2,
|
|
+ PRE_EMPHASIS_LEVEL_3,
|
|
+};
|
|
+
|
|
+enum pattern_set {
|
|
+ PTS1 = BIT(0),
|
|
+ PTS2 = BIT(1),
|
|
+ PTS3 = BIT(2),
|
|
+ PTS4 = BIT(3),
|
|
+ DP_NONE = BIT(4)
|
|
+};
|
|
+
|
|
+enum vic_color_depth {
|
|
+ BCS_6 = 0x1,
|
|
+ BCS_8 = 0x2,
|
|
+ BCS_10 = 0x4,
|
|
+ BCS_12 = 0x8,
|
|
+ BCS_16 = 0x10,
|
|
+};
|
|
+
|
|
+enum vic_bt_type {
|
|
+ BT_601 = 0x0,
|
|
+ BT_709 = 0x1,
|
|
+};
|
|
+
|
|
+enum {
|
|
+ MODE_DVI,
|
|
+ MODE_HDMI_1_4,
|
|
+ MODE_HDMI_2_0,
|
|
+};
|
|
+
|
|
+struct video_info {
|
|
+ int bpc;
|
|
+ int color_fmt;
|
|
+};
|
|
+
|
|
+struct cdns_hdmi_i2c {
|
|
+ struct i2c_adapter adap;
|
|
+
|
|
+ struct mutex lock; /* used to serialize data transfers */
|
|
+ struct completion cmp;
|
|
+ u8 stat;
|
|
+
|
|
+ u8 slave_reg;
|
|
+ bool is_regaddr;
|
|
+ bool is_segment;
|
|
+};
|
|
+
|
|
+struct cdns_mhdp8501_device {
|
|
+ struct cdns_mhdp_base base;
|
|
+
|
|
+ struct device *dev;
|
|
+ void __iomem *regs;
|
|
+ struct drm_connector *curr_conn;
|
|
+ struct drm_bridge bridge;
|
|
+ struct clk *apb_clk;
|
|
+ struct phy *phy;
|
|
+
|
|
+ struct video_info video_info;
|
|
+
|
|
+ int irq[IRQ_NUM];
|
|
+ struct delayed_work hotplug_work;
|
|
+ int connector_type;
|
|
+ u32 lane_mapping;
|
|
+
|
|
+ union {
|
|
+ struct _dp_data {
|
|
+ u32 rate;
|
|
+ u8 num_lanes;
|
|
+ struct drm_dp_aux aux;
|
|
+ u8 dpcd[DP_RECEIVER_CAP_SIZE];
|
|
+ } dp;
|
|
+ struct _hdmi_data {
|
|
+ u32 hdmi_type;
|
|
+ struct cdns_hdmi_i2c *i2c;
|
|
+ } hdmi;
|
|
+ };
|
|
+};
|
|
+
|
|
+extern const struct drm_bridge_funcs cdns_dp_bridge_funcs;
|
|
+extern const struct drm_bridge_funcs cdns_hdmi_bridge_funcs;
|
|
+
|
|
+enum drm_connector_status
|
|
+cdns_mhdp8501_detect(struct drm_bridge *bridge);
|
|
+enum drm_mode_status
|
|
+cdns_mhdp8501_mode_valid(struct drm_bridge *bridge,
|
|
+ const struct drm_display_info *info,
|
|
+ const struct drm_display_mode *mode);
|
|
+
|
|
+ssize_t cdns_dp_aux_transfer(struct drm_dp_aux *aux, struct drm_dp_aux_msg *msg);
|
|
+int cdns_dp_aux_destroy(struct cdns_mhdp8501_device *mhdp);
|
|
+void cdns_dp_check_link_state(struct cdns_mhdp8501_device *mhdp);
|
|
+
|
|
+void cdns_hdmi_handle_hotplug(struct cdns_mhdp8501_device *mhdp);
|
|
+struct i2c_adapter *cdns_hdmi_i2c_adapter(struct cdns_mhdp8501_device *mhdp);
|
|
+#endif
|
|
diff --git a/drivers/gpu/drm/bridge/cadence/cdns-mhdp8501-dp.c b/drivers/gpu/drm/bridge/cadence/cdns-mhdp8501-dp.c
|
|
new file mode 100644
|
|
index 0000000000000..157b4d44b9e2b
|
|
--- /dev/null
|
|
+++ b/drivers/gpu/drm/bridge/cadence/cdns-mhdp8501-dp.c
|
|
@@ -0,0 +1,694 @@
|
|
+// SPDX-License-Identifier: GPL-2.0-only
|
|
+/*
|
|
+ * Cadence MHDP8501 DisplayPort(DP) bridge driver
|
|
+ *
|
|
+ * Copyright (C) 2019-2024 NXP Semiconductor, Inc.
|
|
+ *
|
|
+ */
|
|
+#include <drm/drm_atomic_helper.h>
|
|
+#include <drm/drm_edid.h>
|
|
+#include <drm/drm_print.h>
|
|
+#include <linux/media-bus-format.h>
|
|
+#include <linux/phy/phy.h>
|
|
+#include <linux/phy/phy-dp.h>
|
|
+
|
|
+#include "cdns-mhdp8501-core.h"
|
|
+
|
|
+#define LINK_TRAINING_TIMEOUT_MS 500
|
|
+#define LINK_TRAINING_RETRY_MS 20
|
|
+
|
|
+ssize_t cdns_dp_aux_transfer(struct drm_dp_aux *aux,
|
|
+ struct drm_dp_aux_msg *msg)
|
|
+{
|
|
+ struct cdns_mhdp8501_device *mhdp = dev_get_drvdata(aux->dev);
|
|
+ bool native = msg->request & (DP_AUX_NATIVE_WRITE & DP_AUX_NATIVE_READ);
|
|
+ int ret;
|
|
+
|
|
+ /* Ignore address only message */
|
|
+ if (!msg->size || !msg->buffer) {
|
|
+ msg->reply = native ?
|
|
+ DP_AUX_NATIVE_REPLY_ACK : DP_AUX_I2C_REPLY_ACK;
|
|
+ return msg->size;
|
|
+ }
|
|
+
|
|
+ if (!native) {
|
|
+ dev_err(mhdp->dev, "%s: only native messages supported\n", __func__);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ /* msg sanity check */
|
|
+ if (msg->size > DP_AUX_MAX_PAYLOAD_BYTES) {
|
|
+ dev_err(mhdp->dev, "%s: invalid msg: size(%zu), request(%x)\n",
|
|
+ __func__, msg->size, (unsigned int)msg->request);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ if (msg->request == DP_AUX_NATIVE_WRITE) {
|
|
+ const u8 *buf = msg->buffer;
|
|
+ int i;
|
|
+
|
|
+ for (i = 0; i < msg->size; ++i) {
|
|
+ ret = cdns_mhdp_dpcd_write(&mhdp->base,
|
|
+ msg->address + i, buf[i]);
|
|
+ if (ret < 0) {
|
|
+ dev_err(mhdp->dev, "Failed to write DPCD\n");
|
|
+ return ret;
|
|
+ }
|
|
+ }
|
|
+ msg->reply = DP_AUX_NATIVE_REPLY_ACK;
|
|
+ return msg->size;
|
|
+ }
|
|
+
|
|
+ if (msg->request == DP_AUX_NATIVE_READ) {
|
|
+ ret = cdns_mhdp_dpcd_read(&mhdp->base, msg->address,
|
|
+ msg->buffer, msg->size);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+ msg->reply = DP_AUX_NATIVE_REPLY_ACK;
|
|
+ return msg->size;
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int cdns_dp_aux_destroy(struct cdns_mhdp8501_device *mhdp)
|
|
+{
|
|
+ drm_dp_aux_unregister(&mhdp->dp.aux);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int cdns_dp_get_msa_misc(struct video_info *video)
|
|
+{
|
|
+ u32 msa_misc;
|
|
+ u8 color_space = 0;
|
|
+ u8 bpc = 0;
|
|
+
|
|
+ switch (video->color_fmt) {
|
|
+ /* set YUV default color space conversion to BT601 */
|
|
+ case DRM_COLOR_FORMAT_YCBCR444:
|
|
+ color_space = 6 + BT_601 * 8;
|
|
+ break;
|
|
+ case DRM_COLOR_FORMAT_YCBCR422:
|
|
+ color_space = 5 + BT_601 * 8;
|
|
+ break;
|
|
+ case DRM_COLOR_FORMAT_YCBCR420:
|
|
+ color_space = 5;
|
|
+ break;
|
|
+ case DRM_COLOR_FORMAT_RGB444:
|
|
+ default:
|
|
+ color_space = 0;
|
|
+ break;
|
|
+ };
|
|
+
|
|
+ switch (video->bpc) {
|
|
+ case 6:
|
|
+ bpc = 0;
|
|
+ break;
|
|
+ case 10:
|
|
+ bpc = 2;
|
|
+ break;
|
|
+ case 12:
|
|
+ bpc = 3;
|
|
+ break;
|
|
+ case 16:
|
|
+ bpc = 4;
|
|
+ break;
|
|
+ case 8:
|
|
+ default:
|
|
+ bpc = 1;
|
|
+ break;
|
|
+ };
|
|
+
|
|
+ msa_misc = (bpc << 5) | (color_space << 1);
|
|
+
|
|
+ return msa_misc;
|
|
+}
|
|
+
|
|
+static int cdns_dp_config_video(struct cdns_mhdp8501_device *mhdp,
|
|
+ const struct drm_display_mode *mode)
|
|
+{
|
|
+ struct video_info *video = &mhdp->video_info;
|
|
+ bool h_sync_polarity, v_sync_polarity;
|
|
+ u64 symbol;
|
|
+ u32 val, link_rate, rem;
|
|
+ u8 bit_per_pix, tu_size_reg = TU_SIZE;
|
|
+ int ret;
|
|
+
|
|
+ bit_per_pix = (video->color_fmt == DRM_COLOR_FORMAT_YCBCR422) ?
|
|
+ (video->bpc * 2) : (video->bpc * 3);
|
|
+
|
|
+ link_rate = mhdp->dp.rate / 1000;
|
|
+
|
|
+ ret = cdns_mhdp_reg_write(&mhdp->base, BND_HSYNC2VSYNC, VIF_BYPASS_INTERLACE);
|
|
+ if (ret)
|
|
+ goto err_config_video;
|
|
+
|
|
+ ret = cdns_mhdp_reg_write(&mhdp->base, HSYNC2VSYNC_POL_CTRL, 0);
|
|
+ if (ret)
|
|
+ goto err_config_video;
|
|
+
|
|
+ /*
|
|
+ * get a best tu_size and valid symbol:
|
|
+ * 1. chose Lclk freq(162Mhz, 270Mhz, 540Mhz), set TU to 32
|
|
+ * 2. calculate VS(valid symbol) = TU * Pclk * Bpp / (Lclk * Lanes)
|
|
+ * 3. if VS > *.85 or VS < *.1 or VS < 2 or TU < VS + 4, then set
|
|
+ * TU += 2 and repeat 2nd step.
|
|
+ */
|
|
+ do {
|
|
+ tu_size_reg += 2;
|
|
+ symbol = tu_size_reg * mode->clock * bit_per_pix;
|
|
+ do_div(symbol, mhdp->dp.num_lanes * link_rate * 8);
|
|
+ rem = do_div(symbol, 1000);
|
|
+ if (tu_size_reg > 64) {
|
|
+ ret = -EINVAL;
|
|
+ dev_err(mhdp->dev, "tu error, clk:%d, lanes:%d, rate:%d\n",
|
|
+ mode->clock, mhdp->dp.num_lanes, link_rate);
|
|
+ goto err_config_video;
|
|
+ }
|
|
+ } while ((symbol <= 1) || (tu_size_reg - symbol < 4) ||
|
|
+ (rem > 850) || (rem < 100));
|
|
+
|
|
+ val = symbol + (tu_size_reg << 8);
|
|
+ val |= TU_CNT_RST_EN;
|
|
+ ret = cdns_mhdp_reg_write(&mhdp->base, DP_FRAMER_TU, val);
|
|
+ if (ret)
|
|
+ goto err_config_video;
|
|
+
|
|
+ /* set the FIFO Buffer size */
|
|
+ val = div_u64(mode->clock * (symbol + 1), 1000) + link_rate;
|
|
+ val /= (mhdp->dp.num_lanes * link_rate);
|
|
+ val = div_u64(8 * (symbol + 1), bit_per_pix) - val;
|
|
+ val += 2;
|
|
+ ret = cdns_mhdp_reg_write(&mhdp->base, DP_VC_TABLE(15), val);
|
|
+
|
|
+ switch (video->bpc) {
|
|
+ case 6:
|
|
+ val = BCS_6;
|
|
+ break;
|
|
+ case 10:
|
|
+ val = BCS_10;
|
|
+ break;
|
|
+ case 12:
|
|
+ val = BCS_12;
|
|
+ break;
|
|
+ case 16:
|
|
+ val = BCS_16;
|
|
+ break;
|
|
+ case 8:
|
|
+ default:
|
|
+ val = BCS_8;
|
|
+ break;
|
|
+ };
|
|
+
|
|
+ val += video->color_fmt << 8;
|
|
+ ret = cdns_mhdp_reg_write(&mhdp->base, DP_FRAMER_PXL_REPR, val);
|
|
+ if (ret)
|
|
+ goto err_config_video;
|
|
+
|
|
+ v_sync_polarity = !!(mode->flags & DRM_MODE_FLAG_NVSYNC);
|
|
+ h_sync_polarity = !!(mode->flags & DRM_MODE_FLAG_NHSYNC);
|
|
+
|
|
+ val = h_sync_polarity ? DP_FRAMER_SP_HSP : 0;
|
|
+ val |= v_sync_polarity ? DP_FRAMER_SP_VSP : 0;
|
|
+ ret = cdns_mhdp_reg_write(&mhdp->base, DP_FRAMER_SP, val);
|
|
+ if (ret)
|
|
+ goto err_config_video;
|
|
+
|
|
+ val = (mode->hsync_start - mode->hdisplay) << 16;
|
|
+ val |= mode->htotal - mode->hsync_end;
|
|
+ ret = cdns_mhdp_reg_write(&mhdp->base, DP_FRONT_BACK_PORCH, val);
|
|
+ if (ret)
|
|
+ goto err_config_video;
|
|
+
|
|
+ val = mode->hdisplay * bit_per_pix / 8;
|
|
+ ret = cdns_mhdp_reg_write(&mhdp->base, DP_BYTE_COUNT, val);
|
|
+ if (ret)
|
|
+ goto err_config_video;
|
|
+
|
|
+ val = mode->htotal | ((mode->htotal - mode->hsync_start) << 16);
|
|
+ ret = cdns_mhdp_reg_write(&mhdp->base, MSA_HORIZONTAL_0, val);
|
|
+ if (ret)
|
|
+ goto err_config_video;
|
|
+
|
|
+ val = mode->hsync_end - mode->hsync_start;
|
|
+ val |= (mode->hdisplay << 16) | (h_sync_polarity << 15);
|
|
+ ret = cdns_mhdp_reg_write(&mhdp->base, MSA_HORIZONTAL_1, val);
|
|
+ if (ret)
|
|
+ goto err_config_video;
|
|
+
|
|
+ val = mode->vtotal;
|
|
+ val |= (mode->vtotal - mode->vsync_start) << 16;
|
|
+ ret = cdns_mhdp_reg_write(&mhdp->base, MSA_VERTICAL_0, val);
|
|
+ if (ret)
|
|
+ goto err_config_video;
|
|
+
|
|
+ val = mode->vsync_end - mode->vsync_start;
|
|
+ val |= (mode->vdisplay << 16) | (v_sync_polarity << 15);
|
|
+ ret = cdns_mhdp_reg_write(&mhdp->base, MSA_VERTICAL_1, val);
|
|
+ if (ret)
|
|
+ goto err_config_video;
|
|
+
|
|
+ val = cdns_dp_get_msa_misc(video);
|
|
+ ret = cdns_mhdp_reg_write(&mhdp->base, MSA_MISC, val);
|
|
+ if (ret)
|
|
+ goto err_config_video;
|
|
+
|
|
+ ret = cdns_mhdp_reg_write(&mhdp->base, STREAM_CONFIG, 1);
|
|
+ if (ret)
|
|
+ goto err_config_video;
|
|
+
|
|
+ val = mode->hsync_end - mode->hsync_start;
|
|
+ val |= mode->hdisplay << 16;
|
|
+ ret = cdns_mhdp_reg_write(&mhdp->base, DP_HORIZONTAL, val);
|
|
+ if (ret)
|
|
+ goto err_config_video;
|
|
+
|
|
+ val = mode->vdisplay;
|
|
+ val |= (mode->vtotal - mode->vsync_start) << 16;
|
|
+ ret = cdns_mhdp_reg_write(&mhdp->base, DP_VERTICAL_0, val);
|
|
+ if (ret)
|
|
+ goto err_config_video;
|
|
+
|
|
+ val = mode->vtotal;
|
|
+ ret = cdns_mhdp_reg_write(&mhdp->base, DP_VERTICAL_1, val);
|
|
+ if (ret)
|
|
+ goto err_config_video;
|
|
+
|
|
+ ret = cdns_mhdp_dp_reg_write_bit(&mhdp->base, DP_VB_ID, 2, 1, 0);
|
|
+
|
|
+err_config_video:
|
|
+ if (ret)
|
|
+ dev_err(mhdp->dev, "config video failed: %d\n", ret);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static void cdns_dp_pixel_clk_reset(struct cdns_mhdp8501_device *mhdp)
|
|
+{
|
|
+ u32 val;
|
|
+
|
|
+ /* reset pixel clk */
|
|
+ cdns_mhdp_reg_read(&mhdp->base, SOURCE_HDTX_CAR, &val);
|
|
+ cdns_mhdp_reg_write(&mhdp->base, SOURCE_HDTX_CAR, val & 0xFD);
|
|
+ cdns_mhdp_reg_write(&mhdp->base, SOURCE_HDTX_CAR, val);
|
|
+}
|
|
+
|
|
+static int cdns_dp_set_video_status(struct cdns_mhdp8501_device *mhdp, int active)
|
|
+{
|
|
+ u8 msg;
|
|
+ int ret;
|
|
+
|
|
+ msg = !!active;
|
|
+
|
|
+ ret = cdns_mhdp_mailbox_send(&mhdp->base, MB_MODULE_ID_DP_TX,
|
|
+ DPTX_SET_VIDEO, sizeof(msg), &msg);
|
|
+ if (ret)
|
|
+ dev_err(mhdp->dev, "set video status failed: %d\n", ret);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int cdns_dp_training_start(struct cdns_mhdp8501_device *mhdp)
|
|
+{
|
|
+ unsigned long timeout;
|
|
+ u8 msg, event[2];
|
|
+ int ret;
|
|
+
|
|
+ msg = LINK_TRAINING_RUN;
|
|
+
|
|
+ /* start training */
|
|
+ ret = cdns_mhdp_mailbox_send(&mhdp->base, MB_MODULE_ID_DP_TX,
|
|
+ DPTX_TRAINING_CONTROL, sizeof(msg), &msg);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ timeout = jiffies + msecs_to_jiffies(LINK_TRAINING_TIMEOUT_MS);
|
|
+ while (time_before(jiffies, timeout)) {
|
|
+ msleep(LINK_TRAINING_RETRY_MS);
|
|
+ ret = cdns_mhdp_mailbox_send_recv(&mhdp->base, MB_MODULE_ID_DP_TX,
|
|
+ DPTX_READ_EVENT,
|
|
+ 0, NULL, sizeof(event), event);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ if (event[1] & CLK_RECOVERY_FAILED)
|
|
+ dev_err(mhdp->dev, "clock recovery failed\n");
|
|
+ else if (event[1] & EQ_PHASE_FINISHED)
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ return -ETIMEDOUT;
|
|
+}
|
|
+
|
|
+static int cdns_dp_get_training_status(struct cdns_mhdp8501_device *mhdp)
|
|
+{
|
|
+ u8 status[13];
|
|
+ int ret;
|
|
+
|
|
+ ret = cdns_mhdp_mailbox_send_recv(&mhdp->base, MB_MODULE_ID_DP_TX,
|
|
+ DPTX_READ_LINK_STAT,
|
|
+ 0, NULL, sizeof(status), status);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ mhdp->dp.rate = drm_dp_bw_code_to_link_rate(status[0]);
|
|
+ mhdp->dp.num_lanes = status[1];
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int cdns_dp_train_link(struct cdns_mhdp8501_device *mhdp)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ ret = cdns_dp_training_start(mhdp);
|
|
+ if (ret) {
|
|
+ dev_err(mhdp->dev, "Failed to start training %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = cdns_dp_get_training_status(mhdp);
|
|
+ if (ret) {
|
|
+ dev_err(mhdp->dev, "Failed to get training stat %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ dev_dbg(mhdp->dev, "rate:0x%x, lanes:%d\n", mhdp->dp.rate,
|
|
+ mhdp->dp.num_lanes);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int cdns_dp_set_host_cap(struct cdns_mhdp8501_device *mhdp)
|
|
+{
|
|
+ u8 msg[8];
|
|
+ int ret;
|
|
+
|
|
+ msg[0] = drm_dp_link_rate_to_bw_code(mhdp->dp.rate);
|
|
+ msg[1] = mhdp->dp.num_lanes | SCRAMBLER_EN;
|
|
+ msg[2] = VOLTAGE_LEVEL_2;
|
|
+ msg[3] = PRE_EMPHASIS_LEVEL_3;
|
|
+ msg[4] = PTS1 | PTS2 | PTS3 | PTS4;
|
|
+ msg[5] = FAST_LT_NOT_SUPPORT;
|
|
+ msg[6] = mhdp->lane_mapping;
|
|
+ msg[7] = ENHANCED;
|
|
+
|
|
+ ret = cdns_mhdp_mailbox_send(&mhdp->base, MB_MODULE_ID_DP_TX,
|
|
+ DPTX_SET_HOST_CAPABILITIES,
|
|
+ sizeof(msg), msg);
|
|
+ if (ret)
|
|
+ dev_err(mhdp->dev, "set host cap failed: %d\n", ret);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int cdns_dp_get_edid_block(void *data, u8 *edid,
|
|
+ unsigned int block, size_t length)
|
|
+{
|
|
+ struct cdns_mhdp8501_device *mhdp = data;
|
|
+ u8 msg[2], reg[2], i;
|
|
+ int ret;
|
|
+
|
|
+ for (i = 0; i < 4; i++) {
|
|
+ msg[0] = block / 2;
|
|
+ msg[1] = block % 2;
|
|
+
|
|
+ ret = cdns_mhdp_mailbox_send_recv_multi(&mhdp->base,
|
|
+ MB_MODULE_ID_DP_TX,
|
|
+ DPTX_GET_EDID,
|
|
+ sizeof(msg), msg,
|
|
+ DPTX_GET_EDID,
|
|
+ sizeof(reg), reg,
|
|
+ length, edid);
|
|
+ if (ret)
|
|
+ continue;
|
|
+
|
|
+ if (reg[0] == length && reg[1] == block / 2)
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ if (ret)
|
|
+ dev_err(mhdp->dev, "get block[%d] edid failed: %d\n",
|
|
+ block, ret);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static void cdns_dp_mode_set(struct cdns_mhdp8501_device *mhdp,
|
|
+ const struct drm_display_mode *mode)
|
|
+{
|
|
+ union phy_configure_opts phy_cfg;
|
|
+ int ret;
|
|
+
|
|
+ cdns_dp_pixel_clk_reset(mhdp);
|
|
+
|
|
+ /* Get DP Caps */
|
|
+ ret = drm_dp_dpcd_read(&mhdp->dp.aux, DP_DPCD_REV, mhdp->dp.dpcd,
|
|
+ DP_RECEIVER_CAP_SIZE);
|
|
+ if (ret < 0) {
|
|
+ dev_err(mhdp->dev, "Failed to get caps %d\n", ret);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ mhdp->dp.rate = drm_dp_max_link_rate(mhdp->dp.dpcd);
|
|
+ mhdp->dp.num_lanes = drm_dp_max_lane_count(mhdp->dp.dpcd);
|
|
+
|
|
+ /* check the max link rate */
|
|
+ if (mhdp->dp.rate > CDNS_DP_MAX_LINK_RATE)
|
|
+ mhdp->dp.rate = CDNS_DP_MAX_LINK_RATE;
|
|
+
|
|
+ phy_cfg.dp.lanes = mhdp->dp.num_lanes;
|
|
+ phy_cfg.dp.link_rate = mhdp->dp.rate;
|
|
+ phy_cfg.dp.set_lanes = false;
|
|
+ phy_cfg.dp.set_rate = false;
|
|
+ phy_cfg.dp.set_voltages = false;
|
|
+
|
|
+ ret = phy_configure(mhdp->phy, &phy_cfg);
|
|
+ if (ret) {
|
|
+ dev_err(mhdp->dev, "%s: phy_configure() failed: %d\n",
|
|
+ __func__, ret);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ /* Video off */
|
|
+ ret = cdns_dp_set_video_status(mhdp, CONTROL_VIDEO_IDLE);
|
|
+ if (ret) {
|
|
+ dev_err(mhdp->dev, "Failed to valid video %d\n", ret);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ /* Line swapping */
|
|
+ cdns_mhdp_reg_write(&mhdp->base, LANES_CONFIG, 0x00400000 | mhdp->lane_mapping);
|
|
+
|
|
+ /* Set DP host capability */
|
|
+ ret = cdns_dp_set_host_cap(mhdp);
|
|
+ if (ret) {
|
|
+ dev_err(mhdp->dev, "Failed to set host cap %d\n", ret);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ ret = cdns_mhdp_reg_write(&mhdp->base, DP_AUX_SWAP_INVERSION_CONTROL,
|
|
+ AUX_HOST_INVERT);
|
|
+ if (ret) {
|
|
+ dev_err(mhdp->dev, "Failed to set host invert %d\n", ret);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ ret = cdns_dp_config_video(mhdp, mode);
|
|
+ if (ret)
|
|
+ dev_err(mhdp->dev, "Failed to config video %d\n", ret);
|
|
+}
|
|
+
|
|
+static bool
|
|
+cdns_dp_needs_link_retrain(struct cdns_mhdp8501_device *mhdp)
|
|
+{
|
|
+ u8 link_status[DP_LINK_STATUS_SIZE];
|
|
+
|
|
+ if (drm_dp_dpcd_read_phy_link_status(&mhdp->dp.aux, DP_PHY_DPRX,
|
|
+ link_status) < 0)
|
|
+ return false;
|
|
+
|
|
+ /* Retrain if link not ok */
|
|
+ return !drm_dp_channel_eq_ok(link_status, mhdp->dp.num_lanes);
|
|
+}
|
|
+
|
|
+void cdns_dp_check_link_state(struct cdns_mhdp8501_device *mhdp)
|
|
+{
|
|
+ struct drm_connector *connector = mhdp->curr_conn;
|
|
+ const struct drm_edid *drm_edid;
|
|
+ struct drm_connector_state *conn_state;
|
|
+ struct drm_crtc_state *crtc_state;
|
|
+ struct drm_crtc *crtc;
|
|
+
|
|
+ if (!connector)
|
|
+ return;
|
|
+
|
|
+ /*
|
|
+ * EDID data needs updating after each cable plugin
|
|
+ * due to potential display monitor changes
|
|
+ */
|
|
+ drm_edid = drm_edid_read_custom(connector, cdns_dp_get_edid_block, mhdp);
|
|
+ drm_edid_connector_update(connector, drm_edid);
|
|
+
|
|
+ if (!drm_edid)
|
|
+ return;
|
|
+
|
|
+ drm_edid_free(drm_edid);
|
|
+
|
|
+ conn_state = connector->state;
|
|
+ crtc = conn_state->crtc;
|
|
+ if (!crtc)
|
|
+ return;
|
|
+
|
|
+ crtc_state = crtc->state;
|
|
+ if (!crtc_state->active)
|
|
+ return;
|
|
+
|
|
+ if (!cdns_dp_needs_link_retrain(mhdp))
|
|
+ return;
|
|
+
|
|
+ /* DP link retrain */
|
|
+ if (cdns_dp_train_link(mhdp))
|
|
+ dev_err(mhdp->dev, "Failed link train\n");
|
|
+}
|
|
+
|
|
+static int cdns_dp_bridge_attach(struct drm_bridge *bridge,
|
|
+ enum drm_bridge_attach_flags flags)
|
|
+{
|
|
+ struct cdns_mhdp8501_device *mhdp = bridge->driver_private;
|
|
+
|
|
+ if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) {
|
|
+ dev_err(mhdp->dev, "do not support creating a drm_connector\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ mhdp->dp.aux.drm_dev = bridge->dev;
|
|
+
|
|
+ return drm_dp_aux_register(&mhdp->dp.aux);
|
|
+}
|
|
+
|
|
+static const struct drm_edid
|
|
+*cdns_dp_bridge_edid_read(struct drm_bridge *bridge,
|
|
+ struct drm_connector *connector)
|
|
+{
|
|
+ struct cdns_mhdp8501_device *mhdp = bridge->driver_private;
|
|
+
|
|
+ return drm_edid_read_custom(connector, cdns_dp_get_edid_block, mhdp);
|
|
+}
|
|
+
|
|
+/* Currently supported format */
|
|
+static const u32 mhdp8501_input_fmts[] = {
|
|
+ MEDIA_BUS_FMT_RGB888_1X24,
|
|
+ MEDIA_BUS_FMT_RGB101010_1X30,
|
|
+};
|
|
+
|
|
+static u32 *cdns_dp_bridge_atomic_get_input_bus_fmts(struct drm_bridge *bridge,
|
|
+ struct drm_bridge_state *bridge_state,
|
|
+ struct drm_crtc_state *crtc_state,
|
|
+ struct drm_connector_state *conn_state,
|
|
+ u32 output_fmt,
|
|
+ unsigned int *num_input_fmts)
|
|
+{
|
|
+ u32 *input_fmts;
|
|
+
|
|
+ *num_input_fmts = 0;
|
|
+
|
|
+ input_fmts = kcalloc(ARRAY_SIZE(mhdp8501_input_fmts),
|
|
+ sizeof(*input_fmts),
|
|
+ GFP_KERNEL);
|
|
+ if (!input_fmts)
|
|
+ return NULL;
|
|
+
|
|
+ *num_input_fmts = ARRAY_SIZE(mhdp8501_input_fmts);
|
|
+ memcpy(input_fmts, mhdp8501_input_fmts, sizeof(mhdp8501_input_fmts));
|
|
+
|
|
+ return input_fmts;
|
|
+}
|
|
+
|
|
+static int cdns_dp_bridge_atomic_check(struct drm_bridge *bridge,
|
|
+ struct drm_bridge_state *bridge_state,
|
|
+ struct drm_crtc_state *crtc_state,
|
|
+ struct drm_connector_state *conn_state)
|
|
+{
|
|
+ struct cdns_mhdp8501_device *mhdp = bridge->driver_private;
|
|
+ struct video_info *video = &mhdp->video_info;
|
|
+
|
|
+ if (bridge_state->input_bus_cfg.format == MEDIA_BUS_FMT_RGB888_1X24) {
|
|
+ video->bpc = 8;
|
|
+ video->color_fmt = DRM_COLOR_FORMAT_RGB444;
|
|
+ } else if (bridge_state->input_bus_cfg.format == MEDIA_BUS_FMT_RGB101010_1X30) {
|
|
+ video->bpc = 10;
|
|
+ video->color_fmt = DRM_COLOR_FORMAT_RGB444;
|
|
+ } else {
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void cdns_dp_bridge_atomic_disable(struct drm_bridge *bridge,
|
|
+ struct drm_bridge_state *old_state)
|
|
+{
|
|
+ struct cdns_mhdp8501_device *mhdp = bridge->driver_private;
|
|
+
|
|
+ cdns_dp_set_video_status(mhdp, CONTROL_VIDEO_IDLE);
|
|
+ mhdp->curr_conn = NULL;
|
|
+
|
|
+ phy_power_off(mhdp->phy);
|
|
+}
|
|
+
|
|
+static void cdns_dp_bridge_atomic_enable(struct drm_bridge *bridge,
|
|
+ struct drm_bridge_state *old_state)
|
|
+{
|
|
+ struct cdns_mhdp8501_device *mhdp = bridge->driver_private;
|
|
+ struct drm_atomic_state *state = old_state->base.state;
|
|
+ struct drm_connector *connector;
|
|
+ struct drm_crtc_state *crtc_state;
|
|
+ struct drm_connector_state *conn_state;
|
|
+ int ret;
|
|
+
|
|
+ connector = drm_atomic_get_new_connector_for_encoder(state,
|
|
+ bridge->encoder);
|
|
+ if (WARN_ON(!connector))
|
|
+ return;
|
|
+
|
|
+ mhdp->curr_conn = connector;
|
|
+
|
|
+ conn_state = drm_atomic_get_new_connector_state(state, connector);
|
|
+ if (WARN_ON(!conn_state))
|
|
+ return;
|
|
+
|
|
+ crtc_state = drm_atomic_get_new_crtc_state(state, conn_state->crtc);
|
|
+ if (WARN_ON(!crtc_state))
|
|
+ return;
|
|
+
|
|
+ cdns_dp_mode_set(mhdp, &crtc_state->adjusted_mode);
|
|
+
|
|
+ /* Power up PHY before link training */
|
|
+ phy_power_on(mhdp->phy);
|
|
+
|
|
+ /* Link training */
|
|
+ ret = cdns_dp_train_link(mhdp);
|
|
+ if (ret) {
|
|
+ dev_err(mhdp->dev, "Failed link train %d\n", ret);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ ret = cdns_dp_set_video_status(mhdp, CONTROL_VIDEO_VALID);
|
|
+ if (ret) {
|
|
+ dev_err(mhdp->dev, "Failed to valid video %d\n", ret);
|
|
+ return;
|
|
+ }
|
|
+}
|
|
+
|
|
+const struct drm_bridge_funcs cdns_dp_bridge_funcs = {
|
|
+ .attach = cdns_dp_bridge_attach,
|
|
+ .detect = cdns_mhdp8501_detect,
|
|
+ .edid_read = cdns_dp_bridge_edid_read,
|
|
+ .mode_valid = cdns_mhdp8501_mode_valid,
|
|
+ .atomic_enable = cdns_dp_bridge_atomic_enable,
|
|
+ .atomic_disable = cdns_dp_bridge_atomic_disable,
|
|
+ .atomic_get_input_bus_fmts = cdns_dp_bridge_atomic_get_input_bus_fmts,
|
|
+ .atomic_check = cdns_dp_bridge_atomic_check,
|
|
+ .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
|
|
+ .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
|
|
+ .atomic_reset = drm_atomic_helper_bridge_reset,
|
|
+};
|
|
diff --git a/drivers/gpu/drm/bridge/cadence/cdns-mhdp8501-hdmi.c b/drivers/gpu/drm/bridge/cadence/cdns-mhdp8501-hdmi.c
|
|
new file mode 100644
|
|
index 0000000000000..9556d0929e21d
|
|
--- /dev/null
|
|
+++ b/drivers/gpu/drm/bridge/cadence/cdns-mhdp8501-hdmi.c
|
|
@@ -0,0 +1,745 @@
|
|
+// SPDX-License-Identifier: GPL-2.0-only
|
|
+/*
|
|
+ * Cadence MHDP8501 HDMI bridge driver
|
|
+ *
|
|
+ * Copyright (C) 2019-2024 NXP Semiconductor, Inc.
|
|
+ *
|
|
+ */
|
|
+#include <drm/display/drm_hdmi_helper.h>
|
|
+#include <drm/display/drm_hdmi_state_helper.h>
|
|
+#include <drm/display/drm_scdc_helper.h>
|
|
+#include <drm/drm_atomic_helper.h>
|
|
+#include <drm/drm_edid.h>
|
|
+#include <drm/drm_print.h>
|
|
+#include <linux/phy/phy.h>
|
|
+#include <linux/phy/phy-hdmi.h>
|
|
+
|
|
+#include "cdns-mhdp8501-core.h"
|
|
+
|
|
+/**
|
|
+ * cdns_hdmi_config_infoframe() - fill the HDMI infoframe
|
|
+ * @mhdp: phandle to mhdp device.
|
|
+ * @entry_id: The packet memory address in which the data is written.
|
|
+ * @len: length of infoframe.
|
|
+ * @buf: point to InfoFrame Packet.
|
|
+ * @type: Packet Type of InfoFrame in HDMI Specification.
|
|
+ *
|
|
+ */
|
|
+
|
|
+static void cdns_hdmi_clear_infoframe(struct cdns_mhdp8501_device *mhdp,
|
|
+ u8 entry_id, u8 type)
|
|
+{
|
|
+ u32 val;
|
|
+
|
|
+ /* invalidate entry */
|
|
+ val = F_ACTIVE_IDLE_TYPE(1) | F_PKT_ALLOC_ADDRESS(entry_id) |
|
|
+ F_PACKET_TYPE(type);
|
|
+ writel(val, mhdp->regs + SOURCE_PIF_PKT_ALLOC_REG);
|
|
+ writel(F_PKT_ALLOC_WR_EN(1), mhdp->regs + SOURCE_PIF_PKT_ALLOC_WR_EN);
|
|
+}
|
|
+
|
|
+static void cdns_hdmi_config_infoframe(struct cdns_mhdp8501_device *mhdp,
|
|
+ u8 entry_id, u8 len,
|
|
+ const u8 *buf, u8 type)
|
|
+{
|
|
+ u8 packet[32], packet_len = 32;
|
|
+ u32 packet32, len32;
|
|
+ u32 val, i;
|
|
+
|
|
+ /*
|
|
+ * only support 32 bytes now
|
|
+ * packet[0] = 0
|
|
+ * packet[1-3] = HB[0-2] InfoFrame Packet Header
|
|
+ * packet[4-31 = PB[0-27] InfoFrame Packet Contents
|
|
+ */
|
|
+ if (len >= (packet_len - 1))
|
|
+ return;
|
|
+ packet[0] = 0;
|
|
+ memcpy(packet + 1, buf, len);
|
|
+
|
|
+ cdns_hdmi_clear_infoframe(mhdp, entry_id, type);
|
|
+
|
|
+ /* flush fifo 1 */
|
|
+ writel(F_FIFO1_FLUSH(1), mhdp->regs + SOURCE_PIF_FIFO1_FLUSH);
|
|
+
|
|
+ /* write packet into memory */
|
|
+ len32 = packet_len / 4;
|
|
+ for (i = 0; i < len32; i++) {
|
|
+ packet32 = get_unaligned_le32(packet + 4 * i);
|
|
+ writel(F_DATA_WR(packet32), mhdp->regs + SOURCE_PIF_DATA_WR);
|
|
+ }
|
|
+
|
|
+ /* write entry id */
|
|
+ writel(F_WR_ADDR(entry_id), mhdp->regs + SOURCE_PIF_WR_ADDR);
|
|
+
|
|
+ /* write request */
|
|
+ writel(F_HOST_WR(1), mhdp->regs + SOURCE_PIF_WR_REQ);
|
|
+
|
|
+ /* update entry */
|
|
+ val = F_ACTIVE_IDLE_TYPE(1) | F_TYPE_VALID(1) |
|
|
+ F_PACKET_TYPE(type) | F_PKT_ALLOC_ADDRESS(entry_id);
|
|
+ writel(val, mhdp->regs + SOURCE_PIF_PKT_ALLOC_REG);
|
|
+
|
|
+ writel(F_PKT_ALLOC_WR_EN(1), mhdp->regs + SOURCE_PIF_PKT_ALLOC_WR_EN);
|
|
+}
|
|
+
|
|
+static int cdns_hdmi_get_edid_block(void *data, u8 *edid,
|
|
+ u32 block, size_t length)
|
|
+{
|
|
+ struct cdns_mhdp8501_device *mhdp = data;
|
|
+ u8 msg[2], reg[5], i;
|
|
+ int ret;
|
|
+
|
|
+ for (i = 0; i < 4; i++) {
|
|
+ msg[0] = block / 2;
|
|
+ msg[1] = block % 2;
|
|
+
|
|
+ ret = cdns_mhdp_mailbox_send_recv_multi(&mhdp->base,
|
|
+ MB_MODULE_ID_HDMI_TX,
|
|
+ HDMI_TX_EDID,
|
|
+ sizeof(msg), msg,
|
|
+ HDMI_TX_EDID,
|
|
+ sizeof(reg), reg,
|
|
+ length, edid);
|
|
+
|
|
+ if (ret)
|
|
+ continue;
|
|
+
|
|
+ if ((reg[3] << 8 | reg[4]) == length)
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ if (ret)
|
|
+ dev_err(mhdp->dev, "get block[%d] edid failed: %d\n", block, ret);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int cdns_hdmi_set_hdmi_mode_type(struct cdns_mhdp8501_device *mhdp)
|
|
+{
|
|
+ struct drm_connector_state *conn_state = mhdp->curr_conn->state;
|
|
+ u32 protocol = mhdp->hdmi.hdmi_type;
|
|
+ u32 val;
|
|
+
|
|
+ if (protocol == MODE_HDMI_2_0 &&
|
|
+ conn_state->hdmi.tmds_char_rate >= 340000000) {
|
|
+ cdns_mhdp_reg_write(&mhdp->base, HDTX_CLOCK_REG_0, 0);
|
|
+ cdns_mhdp_reg_write(&mhdp->base, HDTX_CLOCK_REG_1, 0xFFFFF);
|
|
+ }
|
|
+
|
|
+ cdns_mhdp_reg_read(&mhdp->base, HDTX_CONTROLLER, &val);
|
|
+
|
|
+ /* set HDMI mode and preemble mode data enable */
|
|
+ val |= F_HDMI_MODE(protocol) | F_HDMI2_PREAMBLE_EN(1) |
|
|
+ F_HDMI2_CTRL_IL_MODE(1);
|
|
+ return cdns_mhdp_reg_write(&mhdp->base, HDTX_CONTROLLER, val);
|
|
+}
|
|
+
|
|
+static int cdns_hdmi_ctrl_init(struct cdns_mhdp8501_device *mhdp)
|
|
+{
|
|
+ u32 val;
|
|
+ int ret;
|
|
+
|
|
+ /* Set PHY to HDMI data */
|
|
+ ret = cdns_mhdp_reg_write(&mhdp->base, PHY_DATA_SEL, F_SOURCE_PHY_MHDP_SEL(1));
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ ret = cdns_mhdp_reg_write(&mhdp->base, HDTX_HPD,
|
|
+ F_HPD_VALID_WIDTH(4) | F_HPD_GLITCH_WIDTH(0));
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ /* open CARS */
|
|
+ ret = cdns_mhdp_reg_write(&mhdp->base, SOURCE_PHY_CAR, 0xF);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+ ret = cdns_mhdp_reg_write(&mhdp->base, SOURCE_HDTX_CAR, 0xFF);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+ ret = cdns_mhdp_reg_write(&mhdp->base, SOURCE_PKT_CAR, 0xF);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+ ret = cdns_mhdp_reg_write(&mhdp->base, SOURCE_AIF_CAR, 0xF);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+ ret = cdns_mhdp_reg_write(&mhdp->base, SOURCE_CIPHER_CAR, 0xF);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+ ret = cdns_mhdp_reg_write(&mhdp->base, SOURCE_CRYPTO_CAR, 0xF);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+ ret = cdns_mhdp_reg_write(&mhdp->base, SOURCE_CEC_CAR, 3);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ ret = cdns_mhdp_reg_write(&mhdp->base, HDTX_CLOCK_REG_0, 0x7c1f);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+ ret = cdns_mhdp_reg_write(&mhdp->base, HDTX_CLOCK_REG_1, 0x7c1f);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ /* init HDMI Controller */
|
|
+ val = F_BCH_EN(1) | F_PIC_3D(0xF) | F_CLEAR_AVMUTE(1);
|
|
+ ret = cdns_mhdp_reg_write(&mhdp->base, HDTX_CONTROLLER, val);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ return cdns_hdmi_set_hdmi_mode_type(mhdp);
|
|
+}
|
|
+
|
|
+static int cdns_hdmi_mode_config(struct cdns_mhdp8501_device *mhdp,
|
|
+ struct drm_display_mode *mode,
|
|
+ struct drm_connector_hdmi_state *hdmi)
|
|
+{
|
|
+ u32 vsync_lines = mode->vsync_end - mode->vsync_start;
|
|
+ u32 eof_lines = mode->vsync_start - mode->vdisplay;
|
|
+ u32 sof_lines = mode->vtotal - mode->vsync_end;
|
|
+ u32 hblank = mode->htotal - mode->hdisplay;
|
|
+ u32 hactive = mode->hdisplay;
|
|
+ u32 vblank = mode->vtotal - mode->vdisplay;
|
|
+ u32 vactive = mode->vdisplay;
|
|
+ u32 hfront = mode->hsync_start - mode->hdisplay;
|
|
+ u32 hback = mode->htotal - mode->hsync_end;
|
|
+ u32 vfront = eof_lines;
|
|
+ u32 hsync = hblank - hfront - hback;
|
|
+ u32 vsync = vsync_lines;
|
|
+ u32 vback = sof_lines;
|
|
+ u32 v_h_polarity = ((mode->flags & DRM_MODE_FLAG_NHSYNC) ? 0 : 1) +
|
|
+ ((mode->flags & DRM_MODE_FLAG_NVSYNC) ? 0 : 2);
|
|
+ int ret;
|
|
+ u32 val;
|
|
+
|
|
+ ret = cdns_mhdp_reg_write(&mhdp->base, SCHEDULER_H_SIZE, (hactive << 16) + hblank);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ ret = cdns_mhdp_reg_write(&mhdp->base, SCHEDULER_V_SIZE, (vactive << 16) + vblank);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ ret = cdns_mhdp_reg_write(&mhdp->base, HDTX_SIGNAL_FRONT_WIDTH, (vfront << 16) + hfront);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ ret = cdns_mhdp_reg_write(&mhdp->base, HDTX_SIGNAL_SYNC_WIDTH, (vsync << 16) + hsync);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ ret = cdns_mhdp_reg_write(&mhdp->base, HDTX_SIGNAL_BACK_WIDTH, (vback << 16) + hback);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ ret = cdns_mhdp_reg_write(&mhdp->base, HSYNC2VSYNC_POL_CTRL, v_h_polarity);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ /* Reset Data Enable */
|
|
+ cdns_mhdp_reg_read(&mhdp->base, HDTX_CONTROLLER, &val);
|
|
+ val &= ~F_DATA_EN(1);
|
|
+ ret = cdns_mhdp_reg_write(&mhdp->base, HDTX_CONTROLLER, val);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ /* Set bpc */
|
|
+ val &= ~F_VIF_DATA_WIDTH(3);
|
|
+ switch (hdmi->output_bpc) {
|
|
+ case 10:
|
|
+ val |= F_VIF_DATA_WIDTH(1);
|
|
+ break;
|
|
+ case 12:
|
|
+ val |= F_VIF_DATA_WIDTH(2);
|
|
+ break;
|
|
+ case 16:
|
|
+ val |= F_VIF_DATA_WIDTH(3);
|
|
+ break;
|
|
+ case 8:
|
|
+ default:
|
|
+ val |= F_VIF_DATA_WIDTH(0);
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ /* select color encoding */
|
|
+ val &= ~F_HDMI_ENCODING(3);
|
|
+ switch (hdmi->output_format) {
|
|
+ case HDMI_COLORSPACE_YUV444:
|
|
+ val |= F_HDMI_ENCODING(2);
|
|
+ break;
|
|
+ case HDMI_COLORSPACE_YUV422:
|
|
+ val |= F_HDMI_ENCODING(1);
|
|
+ break;
|
|
+ case HDMI_COLORSPACE_YUV420:
|
|
+ val |= F_HDMI_ENCODING(3);
|
|
+ break;
|
|
+ case HDMI_COLORSPACE_RGB:
|
|
+ default:
|
|
+ val |= F_HDMI_ENCODING(0);
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ ret = cdns_mhdp_reg_write(&mhdp->base, HDTX_CONTROLLER, val);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ /* set data enable */
|
|
+ val |= F_DATA_EN(1);
|
|
+ return cdns_mhdp_reg_write(&mhdp->base, HDTX_CONTROLLER, val);
|
|
+}
|
|
+
|
|
+static int cdns_hdmi_disable_gcp(struct cdns_mhdp8501_device *mhdp)
|
|
+{
|
|
+ u32 val;
|
|
+
|
|
+ cdns_mhdp_reg_read(&mhdp->base, HDTX_CONTROLLER, &val);
|
|
+ val &= ~F_GCP_EN(1);
|
|
+
|
|
+ return cdns_mhdp_reg_write(&mhdp->base, HDTX_CONTROLLER, val);
|
|
+}
|
|
+
|
|
+static int cdns_hdmi_enable_gcp(struct cdns_mhdp8501_device *mhdp)
|
|
+{
|
|
+ u32 val;
|
|
+
|
|
+ cdns_mhdp_reg_read(&mhdp->base, HDTX_CONTROLLER, &val);
|
|
+ val |= F_GCP_EN(1);
|
|
+
|
|
+ return cdns_mhdp_reg_write(&mhdp->base, HDTX_CONTROLLER, val);
|
|
+}
|
|
+
|
|
+#define HDMI_14_MAX_TMDS_CLK (340 * 1000 * 1000)
|
|
+static void cdns_hdmi_sink_config(struct cdns_mhdp8501_device *mhdp,
|
|
+ unsigned long long tmds_char_rate)
|
|
+{
|
|
+ struct drm_connector *connector = mhdp->curr_conn;
|
|
+ struct drm_display_info *display = &connector->display_info;
|
|
+ struct drm_scdc *scdc = &display->hdmi.scdc;
|
|
+ bool hdmi_scrambling = false;
|
|
+ bool hdmi_high_tmds_clock_ratio = false;
|
|
+
|
|
+ /* check sink type (HDMI or DVI) */
|
|
+ if (!display->is_hdmi) {
|
|
+ mhdp->hdmi.hdmi_type = MODE_DVI;
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ /* Default work in HDMI1.4 */
|
|
+ mhdp->hdmi.hdmi_type = MODE_HDMI_1_4;
|
|
+
|
|
+ /* check sink support SCDC or not */
|
|
+ if (!scdc->supported) {
|
|
+ dev_dbg(mhdp->dev, "Sink Not Support SCDC\n");
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if (tmds_char_rate > HDMI_14_MAX_TMDS_CLK) {
|
|
+ hdmi_scrambling = true;
|
|
+ hdmi_high_tmds_clock_ratio = true;
|
|
+ mhdp->hdmi.hdmi_type = MODE_HDMI_2_0;
|
|
+ } else if (scdc->scrambling.low_rates) {
|
|
+ hdmi_scrambling = true;
|
|
+ mhdp->hdmi.hdmi_type = MODE_HDMI_2_0;
|
|
+ }
|
|
+
|
|
+ /* Set TMDS bit clock ratio to 1/40 or 1/10, and enable/disable scrambling */
|
|
+ drm_scdc_set_high_tmds_clock_ratio(connector, hdmi_high_tmds_clock_ratio);
|
|
+ drm_scdc_set_scrambling(connector, hdmi_scrambling);
|
|
+}
|
|
+
|
|
+static int cdns_hdmi_bridge_attach(struct drm_bridge *bridge,
|
|
+ enum drm_bridge_attach_flags flags)
|
|
+{
|
|
+ struct cdns_mhdp8501_device *mhdp = bridge->driver_private;
|
|
+
|
|
+ if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) {
|
|
+ dev_err(mhdp->dev, "do not support creating a drm_connector\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int reset_pipe(struct drm_crtc *crtc)
|
|
+{
|
|
+ struct drm_atomic_state *state;
|
|
+ struct drm_crtc_state *crtc_state;
|
|
+ struct drm_modeset_acquire_ctx ctx;
|
|
+ int ret;
|
|
+
|
|
+ state = drm_atomic_state_alloc(crtc->dev);
|
|
+ if (!state)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ drm_modeset_acquire_init(&ctx, 0);
|
|
+
|
|
+ state->acquire_ctx = &ctx;
|
|
+
|
|
+ crtc_state = drm_atomic_get_crtc_state(state, crtc);
|
|
+ if (IS_ERR(crtc_state)) {
|
|
+ ret = PTR_ERR(crtc_state);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ crtc_state->connectors_changed = true;
|
|
+
|
|
+ ret = drm_atomic_commit(state);
|
|
+out:
|
|
+ drm_atomic_state_put(state);
|
|
+ drm_modeset_drop_locks(&ctx);
|
|
+ drm_modeset_acquire_fini(&ctx);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+void cdns_hdmi_handle_hotplug(struct cdns_mhdp8501_device *mhdp)
|
|
+{
|
|
+ struct drm_connector *connector = mhdp->curr_conn;
|
|
+ const struct drm_edid *drm_edid;
|
|
+ struct drm_connector_state *conn_state;
|
|
+ struct drm_crtc_state *crtc_state;
|
|
+ struct drm_crtc *crtc;
|
|
+
|
|
+ if (!connector)
|
|
+ return;
|
|
+
|
|
+ /*
|
|
+ * EDID data needs updating after each cable plugin
|
|
+ * due to potential display monitor changes
|
|
+ */
|
|
+ drm_edid = drm_edid_read_custom(connector, cdns_hdmi_get_edid_block, mhdp);
|
|
+ drm_edid_connector_update(connector, drm_edid);
|
|
+
|
|
+ if (!drm_edid)
|
|
+ return;
|
|
+
|
|
+ drm_edid_free(drm_edid);
|
|
+
|
|
+ conn_state = connector->state;
|
|
+ crtc = conn_state->crtc;
|
|
+ if (!crtc)
|
|
+ return;
|
|
+
|
|
+ crtc_state = crtc->state;
|
|
+ if (!crtc_state->active)
|
|
+ return;
|
|
+
|
|
+ /*
|
|
+ * HDMI 2.0 says that one should not send scrambled data
|
|
+ * prior to configuring the sink scrambling, and that
|
|
+ * TMDS clock/data transmission should be suspended when
|
|
+ * changing the TMDS clock rate in the sink. So let's
|
|
+ * just do a full modeset here, even though some sinks
|
|
+ * would be perfectly happy if were to just reconfigure
|
|
+ * the SCDC settings on the fly.
|
|
+ */
|
|
+ reset_pipe(crtc);
|
|
+}
|
|
+
|
|
+static int cdns_hdmi_i2c_write(struct cdns_mhdp8501_device *mhdp,
|
|
+ struct i2c_msg *msgs)
|
|
+{
|
|
+ u8 msg[5], reg[5];
|
|
+ int ret;
|
|
+
|
|
+ msg[0] = msgs->addr;
|
|
+ msg[1] = msgs->buf[0];
|
|
+ msg[2] = 0;
|
|
+ msg[3] = 1;
|
|
+ msg[4] = msgs->buf[1];
|
|
+
|
|
+ ret = cdns_mhdp_mailbox_send_recv(&mhdp->base,
|
|
+ MB_MODULE_ID_HDMI_TX, HDMI_TX_WRITE,
|
|
+ sizeof(msg), msg, sizeof(reg), reg);
|
|
+ if (ret) {
|
|
+ dev_err(mhdp->dev, "I2C write failed: %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ if (reg[0] != 0)
|
|
+ return -EINVAL;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int cdns_hdmi_i2c_read(struct cdns_mhdp8501_device *mhdp,
|
|
+ struct i2c_msg *msgs, int num)
|
|
+{
|
|
+ u8 msg[4], reg[5];
|
|
+ u8 addr, offset, *buf, len;
|
|
+ int ret, i;
|
|
+
|
|
+ for (i = 0; i < num; i++) {
|
|
+ if (msgs[i].flags & I2C_M_RD) {
|
|
+ addr = msgs[i].addr;
|
|
+ buf = msgs[i].buf;
|
|
+ len = msgs[i].len;
|
|
+ } else {
|
|
+ offset = msgs[i].buf[0];
|
|
+ }
|
|
+ }
|
|
+
|
|
+ msg[0] = addr;
|
|
+ msg[1] = offset;
|
|
+ put_unaligned_be16(len, msg + 2);
|
|
+
|
|
+ ret = cdns_mhdp_mailbox_send_recv_multi(&mhdp->base,
|
|
+ MB_MODULE_ID_HDMI_TX, HDMI_TX_READ,
|
|
+ sizeof(msg), msg,
|
|
+ HDMI_TX_READ,
|
|
+ sizeof(reg), reg,
|
|
+ len, buf);
|
|
+ if (ret) {
|
|
+ dev_err(mhdp->dev, "I2c Read failed: %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+#define SCDC_I2C_SLAVE_ADDRESS 0x54
|
|
+static int cdns_hdmi_i2c_xfer(struct i2c_adapter *adap,
|
|
+ struct i2c_msg *msgs, int num)
|
|
+{
|
|
+ struct cdns_mhdp8501_device *mhdp = i2c_get_adapdata(adap);
|
|
+ struct cdns_hdmi_i2c *i2c = mhdp->hdmi.i2c;
|
|
+ int i, ret = 0;
|
|
+
|
|
+ /*
|
|
+ * MHDP FW provides mailbox APIs for SCDC registers access, but lacks direct I2C APIs.
|
|
+ * While individual I2C registers can be read/written using HDMI general register APIs,
|
|
+ * block reads (e.g., EDID) are not supported, making it a limited I2C interface.
|
|
+ */
|
|
+ for (i = 0; i < num; i++) {
|
|
+ if (msgs[i].addr != SCDC_I2C_SLAVE_ADDRESS) {
|
|
+ dev_err(mhdp->dev, "ADDR=%0x is not supported\n", msgs[i].addr);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ mutex_lock(&i2c->lock);
|
|
+
|
|
+ if (num == 1)
|
|
+ ret = cdns_hdmi_i2c_write(mhdp, msgs);
|
|
+ else
|
|
+ ret = cdns_hdmi_i2c_read(mhdp, msgs, num);
|
|
+
|
|
+ if (!ret)
|
|
+ ret = num;
|
|
+
|
|
+ mutex_unlock(&i2c->lock);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static u32 cdns_hdmi_i2c_func(struct i2c_adapter *adapter)
|
|
+{
|
|
+ return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
|
|
+}
|
|
+
|
|
+static const struct i2c_algorithm cdns_hdmi_algorithm = {
|
|
+ .master_xfer = cdns_hdmi_i2c_xfer,
|
|
+ .functionality = cdns_hdmi_i2c_func,
|
|
+};
|
|
+
|
|
+struct i2c_adapter *cdns_hdmi_i2c_adapter(struct cdns_mhdp8501_device *mhdp)
|
|
+{
|
|
+ struct i2c_adapter *adap;
|
|
+ struct cdns_hdmi_i2c *i2c;
|
|
+ int ret;
|
|
+
|
|
+ i2c = devm_kzalloc(mhdp->dev, sizeof(*i2c), GFP_KERNEL);
|
|
+ if (!i2c)
|
|
+ return ERR_PTR(-ENOMEM);
|
|
+
|
|
+ mutex_init(&i2c->lock);
|
|
+
|
|
+ adap = &i2c->adap;
|
|
+ adap->owner = THIS_MODULE;
|
|
+ adap->dev.parent = mhdp->dev;
|
|
+ adap->algo = &cdns_hdmi_algorithm;
|
|
+ strscpy(adap->name, "MHDP HDMI", sizeof(adap->name));
|
|
+ i2c_set_adapdata(adap, mhdp);
|
|
+
|
|
+ ret = i2c_add_adapter(adap);
|
|
+ if (ret) {
|
|
+ dev_warn(mhdp->dev, "cannot add %s I2C adapter\n", adap->name);
|
|
+ devm_kfree(mhdp->dev, i2c);
|
|
+ return ERR_PTR(ret);
|
|
+ }
|
|
+
|
|
+ mhdp->hdmi.i2c = i2c;
|
|
+
|
|
+ return adap;
|
|
+}
|
|
+
|
|
+static enum drm_mode_status
|
|
+cdns_hdmi_tmds_char_rate_valid(const struct drm_bridge *bridge,
|
|
+ const struct drm_display_mode *mode,
|
|
+ unsigned long long tmds_rate)
|
|
+{
|
|
+ struct cdns_mhdp8501_device *mhdp = bridge->driver_private;
|
|
+ union phy_configure_opts phy_cfg;
|
|
+ int ret;
|
|
+
|
|
+ phy_cfg.hdmi.tmds_char_rate = tmds_rate;
|
|
+
|
|
+ ret = phy_validate(mhdp->phy, PHY_MODE_HDMI, 0, &phy_cfg);
|
|
+ if (ret < 0)
|
|
+ return MODE_CLOCK_RANGE;
|
|
+
|
|
+ return MODE_OK;
|
|
+}
|
|
+
|
|
+static const struct drm_edid
|
|
+*cdns_hdmi_bridge_edid_read(struct drm_bridge *bridge,
|
|
+ struct drm_connector *connector)
|
|
+{
|
|
+ struct cdns_mhdp8501_device *mhdp = bridge->driver_private;
|
|
+
|
|
+ return drm_edid_read_custom(connector, cdns_hdmi_get_edid_block, mhdp);
|
|
+}
|
|
+
|
|
+static void cdns_hdmi_bridge_atomic_disable(struct drm_bridge *bridge,
|
|
+ struct drm_bridge_state *old_state)
|
|
+{
|
|
+ struct cdns_mhdp8501_device *mhdp = bridge->driver_private;
|
|
+
|
|
+ mhdp->curr_conn = NULL;
|
|
+
|
|
+ phy_power_off(mhdp->phy);
|
|
+}
|
|
+
|
|
+static void cdns_hdmi_bridge_atomic_enable(struct drm_bridge *bridge,
|
|
+ struct drm_bridge_state *old_state)
|
|
+{
|
|
+ struct cdns_mhdp8501_device *mhdp = bridge->driver_private;
|
|
+ struct drm_atomic_state *state = old_state->base.state;
|
|
+ struct drm_connector *connector;
|
|
+ struct drm_crtc_state *crtc_state;
|
|
+ struct drm_connector_state *conn_state;
|
|
+ struct drm_connector_hdmi_state *hdmi;
|
|
+ union phy_configure_opts phy_cfg;
|
|
+ int ret;
|
|
+
|
|
+ connector = drm_atomic_get_new_connector_for_encoder(state,
|
|
+ bridge->encoder);
|
|
+ if (WARN_ON(!connector))
|
|
+ return;
|
|
+
|
|
+ mhdp->curr_conn = connector;
|
|
+
|
|
+ conn_state = drm_atomic_get_new_connector_state(state, connector);
|
|
+ if (WARN_ON(!conn_state))
|
|
+ return;
|
|
+
|
|
+ crtc_state = drm_atomic_get_new_crtc_state(state, conn_state->crtc);
|
|
+ if (WARN_ON(!crtc_state))
|
|
+ return;
|
|
+
|
|
+ drm_atomic_helper_connector_hdmi_update_infoframes(connector, state);
|
|
+
|
|
+ /* Line swapping */
|
|
+ cdns_mhdp_reg_write(&mhdp->base, LANES_CONFIG, 0x00400000 | mhdp->lane_mapping);
|
|
+
|
|
+ hdmi = &conn_state->hdmi;
|
|
+ if (WARN_ON(!hdmi))
|
|
+ return;
|
|
+
|
|
+ phy_cfg.hdmi.tmds_char_rate = hdmi->tmds_char_rate;
|
|
+ ret = phy_configure(mhdp->phy, &phy_cfg);
|
|
+ if (ret) {
|
|
+ dev_err(mhdp->dev, "%s: phy_configure() failed: %d\n",
|
|
+ __func__, ret);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ phy_power_on(mhdp->phy);
|
|
+
|
|
+ cdns_hdmi_sink_config(mhdp, hdmi->tmds_char_rate);
|
|
+
|
|
+ ret = cdns_hdmi_ctrl_init(mhdp);
|
|
+ if (ret < 0) {
|
|
+ dev_err(mhdp->dev, "hdmi ctrl init failed = %d\n", ret);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ /* Config GCP */
|
|
+ if (hdmi->output_bpc == 8)
|
|
+ cdns_hdmi_disable_gcp(mhdp);
|
|
+ else
|
|
+ cdns_hdmi_enable_gcp(mhdp);
|
|
+
|
|
+ ret = cdns_hdmi_mode_config(mhdp, &crtc_state->adjusted_mode, hdmi);
|
|
+ if (ret < 0) {
|
|
+ dev_err(mhdp->dev, "CDN_API_HDMITX_SetVic_blocking ret = %d\n", ret);
|
|
+ return;
|
|
+ }
|
|
+}
|
|
+
|
|
+static int cdns_hdmi_bridge_clear_infoframe(struct drm_bridge *bridge,
|
|
+ enum hdmi_infoframe_type type)
|
|
+{
|
|
+ struct cdns_mhdp8501_device *mhdp = bridge->driver_private;
|
|
+
|
|
+ switch (type) {
|
|
+ case HDMI_INFOFRAME_TYPE_AVI:
|
|
+ cdns_hdmi_clear_infoframe(mhdp, 0, HDMI_INFOFRAME_TYPE_AVI);
|
|
+ break;
|
|
+ case HDMI_INFOFRAME_TYPE_SPD:
|
|
+ cdns_hdmi_clear_infoframe(mhdp, 1, HDMI_INFOFRAME_TYPE_SPD);
|
|
+ break;
|
|
+ case HDMI_INFOFRAME_TYPE_VENDOR:
|
|
+ cdns_hdmi_clear_infoframe(mhdp, 2, HDMI_INFOFRAME_TYPE_VENDOR);
|
|
+ break;
|
|
+ default:
|
|
+ dev_dbg(mhdp->dev, "Unsupported infoframe type %x\n", type);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int cdns_hdmi_bridge_write_infoframe(struct drm_bridge *bridge,
|
|
+ enum hdmi_infoframe_type type,
|
|
+ const u8 *buffer, size_t len)
|
|
+{
|
|
+ struct cdns_mhdp8501_device *mhdp = bridge->driver_private;
|
|
+
|
|
+ switch (type) {
|
|
+ case HDMI_INFOFRAME_TYPE_AVI:
|
|
+ cdns_hdmi_config_infoframe(mhdp, 0, len, buffer, HDMI_INFOFRAME_TYPE_AVI);
|
|
+ break;
|
|
+ case HDMI_INFOFRAME_TYPE_SPD:
|
|
+ cdns_hdmi_config_infoframe(mhdp, 1, len, buffer, HDMI_INFOFRAME_TYPE_SPD);
|
|
+ break;
|
|
+ case HDMI_INFOFRAME_TYPE_VENDOR:
|
|
+ cdns_hdmi_config_infoframe(mhdp, 2, len, buffer, HDMI_INFOFRAME_TYPE_VENDOR);
|
|
+ break;
|
|
+ default:
|
|
+ dev_dbg(mhdp->dev, "Unsupported infoframe type %x\n", type);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int cdns_hdmi_bridge_atomic_check(struct drm_bridge *bridge,
|
|
+ struct drm_bridge_state *bridge_state,
|
|
+ struct drm_crtc_state *crtc_state,
|
|
+ struct drm_connector_state *conn_state)
|
|
+{
|
|
+ return drm_atomic_helper_connector_hdmi_check(conn_state->connector, conn_state->state);
|
|
+}
|
|
+
|
|
+const struct drm_bridge_funcs cdns_hdmi_bridge_funcs = {
|
|
+ .attach = cdns_hdmi_bridge_attach,
|
|
+ .detect = cdns_mhdp8501_detect,
|
|
+ .edid_read = cdns_hdmi_bridge_edid_read,
|
|
+ .mode_valid = cdns_mhdp8501_mode_valid,
|
|
+ .atomic_enable = cdns_hdmi_bridge_atomic_enable,
|
|
+ .atomic_disable = cdns_hdmi_bridge_atomic_disable,
|
|
+ .atomic_check = cdns_hdmi_bridge_atomic_check,
|
|
+ .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
|
|
+ .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
|
|
+ .atomic_reset = drm_atomic_helper_bridge_reset,
|
|
+ .hdmi_clear_infoframe = cdns_hdmi_bridge_clear_infoframe,
|
|
+ .hdmi_write_infoframe = cdns_hdmi_bridge_write_infoframe,
|
|
+ .hdmi_tmds_char_rate_valid = cdns_hdmi_tmds_char_rate_valid,
|
|
+};
|
|
|
|
From patchwork Tue Dec 17 06:51:48 2024
|
|
Content-Type: text/plain; charset="utf-8"
|
|
MIME-Version: 1.0
|
|
Content-Transfer-Encoding: 7bit
|
|
Subject: [v20,6/9] dt-bindings: phy: Add Freescale iMX8MQ DP and HDMI PHY
|
|
From: Sandor Yu <Sandor.yu@nxp.com>
|
|
X-Patchwork-Id: 629293
|
|
Message-Id:
|
|
<1f01892a4e462d451a21ddcb4b114283d998cd1b.1734340233.git.Sandor.yu@nxp.com>
|
|
To: dmitry.baryshkov@linaro.org, andrzej.hajda@intel.com,
|
|
neil.armstrong@linaro.org, Laurent.pinchart@ideasonboard.com,
|
|
jonas@kwiboo.se, jernej.skrabec@gmail.com, airlied@gmail.com,
|
|
daniel@ffwll.ch, robh+dt@kernel.org, krzysztof.kozlowski+dt@linaro.org,
|
|
shawnguo@kernel.org, s.hauer@pengutronix.de, festevam@gmail.com,
|
|
vkoul@kernel.org, dri-devel@lists.freedesktop.org,
|
|
devicetree@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
|
|
linux-kernel@vger.kernel.org, linux-phy@lists.infradead.org,
|
|
mripard@kernel.org
|
|
Cc: kernel@pengutronix.de, linux-imx@nxp.com, Sandor.yu@nxp.com,
|
|
oliver.brown@nxp.com, alexander.stein@ew.tq-group.com, sam@ravnborg.org,
|
|
Rob Herring <robh@kernel.org>
|
|
Date: Tue, 17 Dec 2024 14:51:48 +0800
|
|
|
|
Add bindings for Freescale iMX8MQ DP and HDMI PHY.
|
|
|
|
Signed-off-by: Sandor Yu <Sandor.yu@nxp.com>
|
|
Reviewed-by: Rob Herring <robh@kernel.org>
|
|
---
|
|
v9->v20:
|
|
*No change.
|
|
|
|
.../bindings/phy/fsl,imx8mq-dp-hdmi-phy.yaml | 51 +++++++++++++++++++
|
|
1 file changed, 51 insertions(+)
|
|
create mode 100644 Documentation/devicetree/bindings/phy/fsl,imx8mq-dp-hdmi-phy.yaml
|
|
|
|
diff --git a/Documentation/devicetree/bindings/phy/fsl,imx8mq-dp-hdmi-phy.yaml b/Documentation/devicetree/bindings/phy/fsl,imx8mq-dp-hdmi-phy.yaml
|
|
new file mode 100644
|
|
index 0000000000000..c17a645e71bad
|
|
--- /dev/null
|
|
+++ b/Documentation/devicetree/bindings/phy/fsl,imx8mq-dp-hdmi-phy.yaml
|
|
@@ -0,0 +1,51 @@
|
|
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
|
|
+%YAML 1.2
|
|
+---
|
|
+$id: http://devicetree.org/schemas/phy/fsl,imx8mq-dp-hdmi-phy.yaml#
|
|
+$schema: http://devicetree.org/meta-schemas/core.yaml#
|
|
+
|
|
+title: Cadence HDP-TX DP/HDMI PHY for Freescale i.MX8MQ SoC
|
|
+
|
|
+maintainers:
|
|
+ - Sandor Yu <sandor.yu@nxp.com>
|
|
+
|
|
+properties:
|
|
+ compatible:
|
|
+ const: fsl,imx8mq-hdptx-phy
|
|
+
|
|
+ reg:
|
|
+ maxItems: 1
|
|
+
|
|
+ clocks:
|
|
+ items:
|
|
+ - description: PHY reference clock.
|
|
+ - description: APB clock.
|
|
+
|
|
+ clock-names:
|
|
+ items:
|
|
+ - const: ref
|
|
+ - const: apb
|
|
+
|
|
+ "#phy-cells":
|
|
+ const: 0
|
|
+
|
|
+required:
|
|
+ - compatible
|
|
+ - reg
|
|
+ - clocks
|
|
+ - clock-names
|
|
+ - "#phy-cells"
|
|
+
|
|
+additionalProperties: false
|
|
+
|
|
+examples:
|
|
+ - |
|
|
+ #include <dt-bindings/clock/imx8mq-clock.h>
|
|
+ #include <dt-bindings/phy/phy.h>
|
|
+ dp_phy: phy@32c00000 {
|
|
+ compatible = "fsl,imx8mq-hdptx-phy";
|
|
+ reg = <0x32c00000 0x100000>;
|
|
+ #phy-cells = <0>;
|
|
+ clocks = <&hdmi_phy_27m>, <&clk IMX8MQ_CLK_DISP_APB_ROOT>;
|
|
+ clock-names = "ref", "apb";
|
|
+ };
|
|
|
|
From patchwork Tue Dec 17 06:51:49 2024
|
|
Content-Type: text/plain; charset="utf-8"
|
|
MIME-Version: 1.0
|
|
Content-Transfer-Encoding: 7bit
|
|
Subject: [v20,7/9] phy: freescale: Add DisplayPort/HDMI Combo-PHY driver for
|
|
i.MX8MQ
|
|
From: Sandor Yu <Sandor.yu@nxp.com>
|
|
X-Patchwork-Id: 629294
|
|
Message-Id:
|
|
<f85a57a2657ffe0e7334b7a7982ebcb3c4889eec.1734340233.git.Sandor.yu@nxp.com>
|
|
To: dmitry.baryshkov@linaro.org, andrzej.hajda@intel.com,
|
|
neil.armstrong@linaro.org, Laurent.pinchart@ideasonboard.com,
|
|
jonas@kwiboo.se, jernej.skrabec@gmail.com, airlied@gmail.com,
|
|
daniel@ffwll.ch, robh+dt@kernel.org, krzysztof.kozlowski+dt@linaro.org,
|
|
shawnguo@kernel.org, s.hauer@pengutronix.de, festevam@gmail.com,
|
|
vkoul@kernel.org, dri-devel@lists.freedesktop.org,
|
|
devicetree@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
|
|
linux-kernel@vger.kernel.org, linux-phy@lists.infradead.org,
|
|
mripard@kernel.org
|
|
Cc: kernel@pengutronix.de, linux-imx@nxp.com, Sandor.yu@nxp.com,
|
|
oliver.brown@nxp.com, alexander.stein@ew.tq-group.com, sam@ravnborg.org
|
|
Date: Tue, 17 Dec 2024 14:51:49 +0800
|
|
|
|
Add Cadence HDP-TX DisplayPort and HDMI PHY driver for i.MX8MQ.
|
|
|
|
Cadence HDP-TX PHY could be put in either DP mode or
|
|
HDMI mode base on the configuration chosen.
|
|
DisplayPort or HDMI PHY mode is configured in the driver.
|
|
|
|
Signed-off-by: Sandor Yu <Sandor.yu@nxp.com>
|
|
Signed-off-by: Alexander Stein <alexander.stein@ew.tq-group.com>
|
|
---
|
|
v19->v20:
|
|
- implify DP configuration handling by directly copying
|
|
the configuration options to the driver's internal structure.
|
|
- return the error code directly instead of logging an error message in `hdptx_clk_enable`
|
|
- Remove redundant ref_clk_rate check
|
|
|
|
v18->v19:
|
|
- Simplify the PLL tables by removing unused and constant data
|
|
- Remove PHY power management, controller driver will handle them.
|
|
- Remove enum dp_link_rate
|
|
- Introduce read_pll_timeout.
|
|
- Update clock management as devm_clk_get_enabled() introduced.
|
|
- Remove cdns_hdptx_phy_init() and cdns_hdptx_phy_remove().
|
|
|
|
v17->v18:
|
|
- fix build error as code rebase to latest kernel version.
|
|
|
|
drivers/phy/freescale/Kconfig | 10 +
|
|
drivers/phy/freescale/Makefile | 1 +
|
|
drivers/phy/freescale/phy-fsl-imx8mq-hdptx.c | 1231 ++++++++++++++++++
|
|
3 files changed, 1242 insertions(+)
|
|
create mode 100644 drivers/phy/freescale/phy-fsl-imx8mq-hdptx.c
|
|
|
|
diff --git a/drivers/phy/freescale/Kconfig b/drivers/phy/freescale/Kconfig
|
|
index dcd9acff6d01a..2b1210367b31c 100644
|
|
--- a/drivers/phy/freescale/Kconfig
|
|
+++ b/drivers/phy/freescale/Kconfig
|
|
@@ -35,6 +35,16 @@ config PHY_FSL_IMX8M_PCIE
|
|
Enable this to add support for the PCIE PHY as found on
|
|
i.MX8M family of SOCs.
|
|
|
|
+config PHY_FSL_IMX8MQ_HDPTX
|
|
+ tristate "Freescale i.MX8MQ DP/HDMI PHY support"
|
|
+ depends on OF && HAS_IOMEM
|
|
+ depends on COMMON_CLK
|
|
+ select GENERIC_PHY
|
|
+ select CDNS_MHDP_HELPER
|
|
+ help
|
|
+ Enable this to support the Cadence HDPTX DP/HDMI PHY driver
|
|
+ on i.MX8MQ SOC.
|
|
+
|
|
config PHY_FSL_IMX8QM_HSIO
|
|
tristate "Freescale i.MX8QM HSIO PHY"
|
|
depends on OF && HAS_IOMEM
|
|
diff --git a/drivers/phy/freescale/Makefile b/drivers/phy/freescale/Makefile
|
|
index 658eac7d0a622..a946b87905498 100644
|
|
--- a/drivers/phy/freescale/Makefile
|
|
+++ b/drivers/phy/freescale/Makefile
|
|
@@ -1,4 +1,5 @@
|
|
# SPDX-License-Identifier: GPL-2.0-only
|
|
+obj-$(CONFIG_PHY_FSL_IMX8MQ_HDPTX) += phy-fsl-imx8mq-hdptx.o
|
|
obj-$(CONFIG_PHY_FSL_IMX8MQ_USB) += phy-fsl-imx8mq-usb.o
|
|
obj-$(CONFIG_PHY_MIXEL_LVDS_PHY) += phy-fsl-imx8qm-lvds-phy.o
|
|
obj-$(CONFIG_PHY_MIXEL_MIPI_DPHY) += phy-fsl-imx8-mipi-dphy.o
|
|
diff --git a/drivers/phy/freescale/phy-fsl-imx8mq-hdptx.c b/drivers/phy/freescale/phy-fsl-imx8mq-hdptx.c
|
|
new file mode 100644
|
|
index 0000000000000..230b7148639b2
|
|
--- /dev/null
|
|
+++ b/drivers/phy/freescale/phy-fsl-imx8mq-hdptx.c
|
|
@@ -0,0 +1,1231 @@
|
|
+// SPDX-License-Identifier: GPL-2.0-only
|
|
+/*
|
|
+ * Cadence DP/HDMI PHY driver
|
|
+ *
|
|
+ * Copyright (C) 2022-2024 NXP Semiconductor, Inc.
|
|
+ */
|
|
+#include <linux/clk.h>
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/phy/phy.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/io.h>
|
|
+#include <linux/unaligned.h>
|
|
+#include <soc/cadence/cdns-mhdp-helper.h>
|
|
+
|
|
+#define ADDR_PHY_AFE 0x80000
|
|
+
|
|
+/* PHY registers */
|
|
+#define CMN_SSM_BIAS_TMR 0x0022
|
|
+#define CMN_PLLSM0_PLLEN_TMR 0x0029
|
|
+#define CMN_PLLSM0_PLLPRE_TMR 0x002a
|
|
+#define CMN_PLLSM0_PLLVREF_TMR 0x002b
|
|
+#define CMN_PLLSM0_PLLLOCK_TMR 0x002c
|
|
+#define CMN_PLLSM0_USER_DEF_CTRL 0x002f
|
|
+#define CMN_PSM_CLK_CTRL 0x0061
|
|
+#define CMN_CDIAG_REFCLK_CTRL 0x0062
|
|
+#define CMN_PLL0_VCOCAL_START 0x0081
|
|
+#define CMN_PLL0_VCOCAL_INIT_TMR 0x0084
|
|
+#define CMN_PLL0_VCOCAL_ITER_TMR 0x0085
|
|
+#define CMN_PLL0_INTDIV 0x0094
|
|
+#define CMN_PLL0_FRACDIV 0x0095
|
|
+#define CMN_PLL0_HIGH_THR 0x0096
|
|
+#define CMN_PLL0_DSM_DIAG 0x0097
|
|
+#define CMN_PLL0_SS_CTRL2 0x0099
|
|
+#define CMN_ICAL_INIT_TMR 0x00c4
|
|
+#define CMN_ICAL_ITER_TMR 0x00c5
|
|
+#define CMN_RXCAL_INIT_TMR 0x00d4
|
|
+#define CMN_RXCAL_ITER_TMR 0x00d5
|
|
+#define CMN_TXPUCAL_CTRL 0x00e0
|
|
+#define CMN_TXPUCAL_INIT_TMR 0x00e4
|
|
+#define CMN_TXPUCAL_ITER_TMR 0x00e5
|
|
+#define CMN_TXPDCAL_CTRL 0x00f0
|
|
+#define CMN_TXPDCAL_INIT_TMR 0x00f4
|
|
+#define CMN_TXPDCAL_ITER_TMR 0x00f5
|
|
+#define CMN_ICAL_ADJ_INIT_TMR 0x0102
|
|
+#define CMN_ICAL_ADJ_ITER_TMR 0x0103
|
|
+#define CMN_RX_ADJ_INIT_TMR 0x0106
|
|
+#define CMN_RX_ADJ_ITER_TMR 0x0107
|
|
+#define CMN_TXPU_ADJ_CTRL 0x0108
|
|
+#define CMN_TXPU_ADJ_INIT_TMR 0x010a
|
|
+#define CMN_TXPU_ADJ_ITER_TMR 0x010b
|
|
+#define CMN_TXPD_ADJ_CTRL 0x010c
|
|
+#define CMN_TXPD_ADJ_INIT_TMR 0x010e
|
|
+#define CMN_TXPD_ADJ_ITER_TMR 0x010f
|
|
+#define CMN_DIAG_PLL0_FBH_OVRD 0x01c0
|
|
+#define CMN_DIAG_PLL0_FBL_OVRD 0x01c1
|
|
+#define CMN_DIAG_PLL0_OVRD 0x01c2
|
|
+#define CMN_DIAG_PLL0_TEST_MODE 0x01c4
|
|
+#define CMN_DIAG_PLL0_V2I_TUNE 0x01c5
|
|
+#define CMN_DIAG_PLL0_CP_TUNE 0x01c6
|
|
+#define CMN_DIAG_PLL0_LF_PROG 0x01c7
|
|
+#define CMN_DIAG_PLL0_PTATIS_TUNE1 0x01c8
|
|
+#define CMN_DIAG_PLL0_PTATIS_TUNE2 0x01c9
|
|
+#define CMN_DIAG_PLL0_INCLK_CTRL 0x01ca
|
|
+#define CMN_DIAG_PLL0_PXL_DIVH 0x01cb
|
|
+#define CMN_DIAG_PLL0_PXL_DIVL 0x01cc
|
|
+#define CMN_DIAG_HSCLK_SEL 0x01e0
|
|
+#define CMN_DIAG_PER_CAL_ADJ 0x01ec
|
|
+#define CMN_DIAG_CAL_CTRL 0x01ed
|
|
+#define CMN_DIAG_ACYA 0x01ff
|
|
+#define XCVR_PSM_RCTRL 0x4001
|
|
+#define XCVR_PSM_CAL_TMR 0x4002
|
|
+#define XCVR_PSM_A0IN_TMR 0x4003
|
|
+#define TX_TXCC_CAL_SCLR_MULT_0 0x4047
|
|
+#define TX_TXCC_CPOST_MULT_00_0 0x404c
|
|
+#define XCVR_DIAG_PLLDRC_CTRL 0x40e0
|
|
+#define XCVR_DIAG_HSCLK_SEL 0x40e1
|
|
+#define XCVR_DIAG_BIDI_CTRL 0x40e8
|
|
+#define XCVR_DIAG_LANE_FCM_EN_MGN_TMR 0x40f2
|
|
+#define TX_PSC_A0 0x4100
|
|
+#define TX_PSC_A1 0x4101
|
|
+#define TX_PSC_A2 0x4102
|
|
+#define TX_PSC_A3 0x4103
|
|
+#define TX_RCVDET_EN_TMR 0x4122
|
|
+#define TX_RCVDET_ST_TMR 0x4123
|
|
+#define TX_DIAG_TX_CTRL 0x41e0
|
|
+#define TX_DIAG_TX_DRV 0x41e1
|
|
+#define TX_DIAG_BGREF_PREDRV_DELAY 0x41e7
|
|
+#define TX_DIAG_ACYA_0 0x41ff
|
|
+#define TX_DIAG_ACYA_1 0x43ff
|
|
+#define TX_DIAG_ACYA_2 0x45ff
|
|
+#define TX_DIAG_ACYA_3 0x47ff
|
|
+#define TX_ANA_CTRL_REG_1 0x5020
|
|
+#define TX_ANA_CTRL_REG_2 0x5021
|
|
+#define TX_DIG_CTRL_REG_1 0x5023
|
|
+#define TX_DIG_CTRL_REG_2 0x5024
|
|
+#define TXDA_CYA_AUXDA_CYA 0x5025
|
|
+#define TX_ANA_CTRL_REG_3 0x5026
|
|
+#define TX_ANA_CTRL_REG_4 0x5027
|
|
+#define TX_ANA_CTRL_REG_5 0x5029
|
|
+#define RX_PSC_A0 0x8000
|
|
+#define RX_PSC_CAL 0x8006
|
|
+#define PHY_HDP_MODE_CTRL 0xc008
|
|
+#define PHY_HDP_CLK_CTL 0xc009
|
|
+#define PHY_ISO_CMN_CTRL 0xc010
|
|
+#define PHY_PMA_CMN_CTRL1 0xc800
|
|
+#define PHY_PMA_ISO_CMN_CTRL 0xc810
|
|
+#define PHY_PMA_ISO_PLL_CTRL1 0xc812
|
|
+#define PHY_PMA_ISOLATION_CTRL 0xc81f
|
|
+
|
|
+/* PHY_HDP_CLK_CTL */
|
|
+#define PLL_DATA_RATE_CLK_DIV_MASK GENMASK(15, 8)
|
|
+#define PLL_DATA_RATE_CLK_DIV_HBR 0x24
|
|
+#define PLL_DATA_RATE_CLK_DIV_HBR2 0x12
|
|
+#define PLL_CLK_EN_ACK BIT(3)
|
|
+#define PLL_CLK_EN BIT(2)
|
|
+#define PLL_READY BIT(1)
|
|
+#define PLL_EN BIT(0)
|
|
+
|
|
+/* PHY_PMA_CMN_CTRL1 */
|
|
+#define CMA_REF_CLK_DIG_DIV_MASK GENMASK(13, 12)
|
|
+#define CMA_REF_CLK_SEL_MASK GENMASK(6, 4)
|
|
+#define CMA_REF_CLK_RCV_EN_MASK BIT(3)
|
|
+#define CMA_REF_CLK_RCV_EN 1
|
|
+#define CMN_READY BIT(0)
|
|
+
|
|
+/* PHY_PMA_ISO_PLL_CTRL1 */
|
|
+#define CMN_PLL0_CLK_DATART_DIV_MASK GENMASK(7, 0)
|
|
+
|
|
+/* TX_DIAG_TX_DRV */
|
|
+#define TX_DRIVER_PROG_BOOST_ENABLE BIT(10)
|
|
+#define TX_DRIVER_PROG_BOOST_LEVEL_MASK GENMASK(9, 8)
|
|
+#define TX_DRIVER_LDO_BG_DEPENDENT_REF_ENABLE BIT(7)
|
|
+#define TX_DRIVER_LDO_BANDGAP_REF_ENABLE BIT(6)
|
|
+
|
|
+/* TX_TXCC_CAL_SCLR_MULT_0 */
|
|
+#define SCALED_RESISTOR_CALIBRATION_CODE_ADD BIT(8)
|
|
+#define RESISTOR_CAL_MULT_VAL_32_128 BIT(5)
|
|
+
|
|
+/* CMN_CDIAG_REFCLK_CTRL */
|
|
+#define DIG_REF_CLK_DIV_SCALER_MASK GENMASK(14, 12)
|
|
+#define REFCLK_TERMINATION_EN_OVERRIDE_EN BIT(7)
|
|
+#define REFCLK_TERMINATION_EN_OVERRIDE BIT(6)
|
|
+
|
|
+/* CMN_DIAG_HSCLK_SEL */
|
|
+#define HSCLK1_SEL_MASK GENMASK(5, 4)
|
|
+#define HSCLK0_SEL_MASK GENMASK(1, 0)
|
|
+#define HSCLK_PLL0_DIV2 1
|
|
+
|
|
+/* XCVR_DIAG_HSCLK_SEL */
|
|
+#define HSCLK_SEL_MODE3_MASK GENMASK(13, 12)
|
|
+#define HSCLK_SEL_MODE3_HSCLK1 1
|
|
+
|
|
+/* CMN_PLL0_VCOCAL_START */
|
|
+#define VCO_CALIB_CODE_START_POINT_VAL_MASK GENMASK(8, 0)
|
|
+
|
|
+/* CMN_DIAG_PLL0_FBH_OVRD */
|
|
+#define PLL_FEEDBACK_DIV_HI_OVERRIDE_EN BIT(15)
|
|
+
|
|
+/* CMN_DIAG_PLL0_FBL_OVRD */
|
|
+#define PLL_FEEDBACK_DIV_LO_OVERRIDE_EN BIT(15)
|
|
+
|
|
+/* CMN_DIAG_PLL0_PXL_DIVH */
|
|
+#define PLL_PCLK_DIV_EN BIT(15)
|
|
+
|
|
+/* XCVR_DIAG_PLLDRC_CTRL */
|
|
+#define DPLL_CLK_SEL_MODE3 BIT(14)
|
|
+#define DPLL_DATA_RATE_DIV_MODE3_MASK GENMASK(13, 12)
|
|
+
|
|
+/* TX_DIAG_TX_CTRL */
|
|
+#define TX_IF_SUBRATE_MODE3_MASK GENMASK(7, 6)
|
|
+
|
|
+/* PHY_HDP_MODE_CTRL */
|
|
+#define POWER_STATE_A3_ACK BIT(7)
|
|
+#define POWER_STATE_A2_ACK BIT(6)
|
|
+#define POWER_STATE_A1_ACK BIT(5)
|
|
+#define POWER_STATE_A0_ACK BIT(4)
|
|
+#define POWER_STATE_A3 BIT(3)
|
|
+#define POWER_STATE_A2 BIT(2)
|
|
+#define POWER_STATE_A1 BIT(1)
|
|
+#define POWER_STATE_A0 BIT(0)
|
|
+
|
|
+/* PHY_PMA_ISO_CMN_CTRL */
|
|
+#define CMN_MACRO_PWR_EN_ACK BIT(5)
|
|
+
|
|
+#define KEEP_ALIVE 0x18
|
|
+
|
|
+/* FW check alive timeout */
|
|
+#define CDNS_KEEP_ALIVE_TIMEOUT 2000
|
|
+#define CDNS_KEEP_ALIVE_MASK GENMASK(7, 0)
|
|
+
|
|
+#define REF_CLK_27MHZ 27000000
|
|
+
|
|
+#define LINK_RATE_2_7 270000
|
|
+#define MAX_LINK_RATE 540000
|
|
+
|
|
+#define CMN_REF_CLK_DIG_DIV 1
|
|
+#define REF_CLK_DIVIDER_SCALER 1
|
|
+
|
|
+/* HDMI TX clock control settings */
|
|
+struct hdptx_hdmi_ctrl {
|
|
+ u32 pixel_clk_freq;
|
|
+ u32 feedback_factor;
|
|
+ u32 cmnda_pll0_ip_div;
|
|
+ u32 pll_fb_div_total;
|
|
+ u32 cmnda_pll0_fb_div_low;
|
|
+ u32 cmnda_pll0_fb_div_high;
|
|
+ u32 cmnda_pll0_pxdiv_low;
|
|
+ u32 cmnda_pll0_pxdiv_high;
|
|
+ u32 vco_ring_select;
|
|
+ u32 cmnda_hs_clk_0_sel;
|
|
+ u32 cmnda_hs_clk_1_sel;
|
|
+ u32 hsclk_div_tx_sub_rate;
|
|
+ u32 cmnda_pll0_hs_sym_div_sel;
|
|
+};
|
|
+
|
|
+struct cdns_hdptx_phy {
|
|
+ struct cdns_mhdp_base base;
|
|
+
|
|
+ void __iomem *regs; /* DPTX registers base */
|
|
+ struct device *dev;
|
|
+ struct phy *phy;
|
|
+ struct clk *ref_clk, *apb_clk;
|
|
+ u32 ref_clk_rate;
|
|
+ union {
|
|
+ struct phy_configure_opts_hdmi hdmi;
|
|
+ struct phy_configure_opts_dp dp;
|
|
+ };
|
|
+};
|
|
+
|
|
+/* HDMI TX clock control settings, pixel clock is output */
|
|
+static const struct hdptx_hdmi_ctrl pixel_clk_output_ctrl_table[] = {
|
|
+ /* clk fbak ipd totl div_l div_h pd_l pd_h v h1 h2 sub sym*/
|
|
+ { 27000, 1000, 3, 240, 0x0bc, 0x30, 0x26, 0x26, 0, 2, 2, 4, 3 },
|
|
+ { 27000, 1250, 3, 300, 0x0ec, 0x3c, 0x30, 0x30, 0, 2, 2, 4, 3 },
|
|
+ { 27000, 1500, 3, 360, 0x11c, 0x48, 0x3a, 0x3a, 0, 2, 2, 4, 3 },
|
|
+ { 27000, 2000, 3, 240, 0x0bc, 0x30, 0x26, 0x26, 0, 2, 2, 4, 2 },
|
|
+ { 54000, 1000, 3, 480, 0x17c, 0x60, 0x26, 0x26, 1, 2, 2, 4, 3 },
|
|
+ { 54000, 1250, 4, 400, 0x13c, 0x50, 0x17, 0x17, 0, 1, 1, 4, 2 },
|
|
+ { 54000, 1500, 4, 480, 0x17c, 0x60, 0x1c, 0x1c, 0, 2, 2, 2, 2 },
|
|
+ { 54000, 2000, 3, 240, 0x0bc, 0x30, 0x12, 0x12, 0, 2, 2, 1, 1 },
|
|
+ { 74250, 1000, 3, 660, 0x20c, 0x84, 0x26, 0x26, 1, 2, 2, 4, 3 },
|
|
+ { 74250, 1250, 4, 550, 0x1b4, 0x6e, 0x17, 0x17, 1, 1, 1, 4, 2 },
|
|
+ { 74250, 1500, 4, 660, 0x20c, 0x84, 0x1c, 0x1c, 1, 2, 2, 2, 2 },
|
|
+ { 74250, 2000, 3, 330, 0x104, 0x42, 0x12, 0x12, 0, 2, 2, 1, 1 },
|
|
+ { 99000, 1000, 3, 440, 0x15c, 0x58, 0x12, 0x12, 1, 2, 2, 2, 2 },
|
|
+ { 99000, 1250, 3, 275, 0x0d8, 0x37, 0x0b, 0x0a, 0, 1, 1, 2, 1 },
|
|
+ { 99000, 1500, 3, 330, 0x104, 0x42, 0x0d, 0x0d, 0, 2, 2, 1, 1 },
|
|
+ { 99000, 2000, 3, 440, 0x15c, 0x58, 0x12, 0x12, 1, 2, 2, 1, 1 },
|
|
+ { 148500, 1000, 3, 660, 0x20c, 0x84, 0x12, 0x12, 1, 2, 2, 2, 2 },
|
|
+ { 148500, 1250, 4, 550, 0x1b4, 0x6e, 0x0b, 0x0a, 1, 1, 1, 2, 1 },
|
|
+ { 148500, 1500, 3, 495, 0x188, 0x63, 0x0d, 0x0d, 1, 1, 1, 2, 1 },
|
|
+ { 148500, 2000, 3, 660, 0x20c, 0x84, 0x12, 0x12, 1, 2, 2, 1, 1 },
|
|
+ { 198000, 1000, 3, 220, 0x0ac, 0x2c, 0x03, 0x03, 0, 1, 1, 1, 0 },
|
|
+ { 198000, 1250, 3, 550, 0x1b4, 0x6e, 0x0b, 0x0a, 1, 1, 1, 2, 1 },
|
|
+ { 198000, 1500, 3, 330, 0x104, 0x42, 0x06, 0x05, 0, 1, 1, 1, 0 },
|
|
+ { 198000, 2000, 3, 440, 0x15c, 0x58, 0x08, 0x08, 1, 1, 1, 1, 0 },
|
|
+ { 297000, 1000, 3, 330, 0x104, 0x42, 0x03, 0x03, 0, 1, 1, 1, 0 },
|
|
+ { 297000, 1500, 3, 495, 0x188, 0x63, 0x06, 0x05, 1, 1, 1, 1, 0 },
|
|
+ { 297000, 2000, 3, 660, 0x20c, 0x84, 0x08, 0x08, 1, 1, 1, 1, 0 },
|
|
+ { 594000, 1000, 3, 660, 0x20c, 0x84, 0x03, 0x03, 1, 1, 1, 1, 0 },
|
|
+ { 594000, 750, 3, 495, 0x188, 0x63, 0x03, 0x03, 1, 1, 1, 1, 0 },
|
|
+ { 594000, 625, 4, 550, 0x1b4, 0x6e, 0x03, 0x03, 1, 1, 1, 1, 0 },
|
|
+ { 594000, 500, 3, 660, 0x20c, 0x84, 0x03, 0x03, 1, 1, 1, 2, 1 },
|
|
+};
|
|
+
|
|
+/* HDMI TX PLL tuning settings */
|
|
+struct hdptx_hdmi_pll_tuning {
|
|
+ u32 vco_freq;
|
|
+ u32 volt_to_current_coarse;
|
|
+ u32 volt_to_current;
|
|
+ u32 ndac_ctrl;
|
|
+ u32 pmos_ctrl;
|
|
+ u32 ptat_ndac_ctrl;
|
|
+ u32 feedback_div_total;
|
|
+ u32 charge_pump_gain;
|
|
+ u32 vco_cal_code;
|
|
+};
|
|
+
|
|
+/* HDMI TX PLL tuning settings, pixel clock is output */
|
|
+static const struct hdptx_hdmi_pll_tuning pixel_clk_output_pll_table[] = {
|
|
+ /*VCO_f coar cu nd pm ptat fd_d gain cal */
|
|
+ { 1980000, 4, 3, 0, 9, 0x9, 220, 0x42, 183 },
|
|
+ { 2160000, 4, 3, 0, 9, 0x9, 240, 0x42, 208 },
|
|
+ { 2475000, 5, 3, 1, 0, 0x7, 275, 0x42, 209 },
|
|
+ { 2700000, 5, 3, 1, 0, 0x7, 300, 0x42, 230 },
|
|
+ { 2700000, 5, 3, 1, 0, 0x7, 400, 0x4c, 230 },
|
|
+ { 2970000, 6, 3, 1, 0, 0x7, 330, 0x42, 225 },
|
|
+ { 3240000, 6, 3, 1, 0, 0x7, 360, 0x42, 256 },
|
|
+ { 3240000, 6, 3, 1, 0, 0x7, 480, 0x4c, 256 },
|
|
+ { 3712500, 4, 3, 0, 7, 0xF, 550, 0x4c, 257 },
|
|
+ { 3960000, 5, 3, 0, 7, 0xF, 440, 0x42, 226 },
|
|
+ { 4320000, 5, 3, 1, 7, 0xF, 480, 0x42, 258 },
|
|
+ { 4455000, 5, 3, 0, 7, 0xF, 495, 0x42, 272 },
|
|
+ { 4455000, 5, 3, 0, 7, 0xF, 660, 0x4c, 272 },
|
|
+ { 4950000, 6, 3, 1, 0, 0x7, 550, 0x42, 258 },
|
|
+ { 5940000, 7, 3, 1, 0, 0x7, 660, 0x42, 292 },
|
|
+};
|
|
+
|
|
+struct phy_pll_reg {
|
|
+ u16 val[7];
|
|
+ u32 addr;
|
|
+};
|
|
+
|
|
+static const struct phy_pll_reg phy_pll_27m_cfg[] = {
|
|
+ /* 1.62 2.16 2.43 2.7 3.24 4.32 5.4 register address */
|
|
+ {{ 0x010e, 0x010e, 0x010e, 0x010e, 0x010e, 0x010e, 0x010e }, CMN_PLL0_VCOCAL_INIT_TMR },
|
|
+ {{ 0x001b, 0x001b, 0x001b, 0x001b, 0x001b, 0x001b, 0x001b }, CMN_PLL0_VCOCAL_ITER_TMR },
|
|
+ {{ 0x30b9, 0x3087, 0x3096, 0x30b4, 0x30b9, 0x3087, 0x30b4 }, CMN_PLL0_VCOCAL_START },
|
|
+ {{ 0x0077, 0x009f, 0x00b3, 0x00c7, 0x0077, 0x009f, 0x00c7 }, CMN_PLL0_INTDIV },
|
|
+ {{ 0xf9da, 0xf7cd, 0xf6c7, 0xf5c1, 0xf9da, 0xf7cd, 0xf5c1 }, CMN_PLL0_FRACDIV },
|
|
+ {{ 0x001e, 0x0028, 0x002d, 0x0032, 0x001e, 0x0028, 0x0032 }, CMN_PLL0_HIGH_THR },
|
|
+ {{ 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020 }, CMN_PLL0_DSM_DIAG },
|
|
+ {{ 0x0000, 0x1000, 0x1000, 0x1000, 0x0000, 0x1000, 0x1000 }, CMN_PLLSM0_USER_DEF_CTRL },
|
|
+ {{ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 }, CMN_DIAG_PLL0_OVRD },
|
|
+ {{ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 }, CMN_DIAG_PLL0_FBH_OVRD },
|
|
+ {{ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 }, CMN_DIAG_PLL0_FBL_OVRD },
|
|
+ {{ 0x0006, 0x0007, 0x0007, 0x0007, 0x0006, 0x0007, 0x0007 }, CMN_DIAG_PLL0_V2I_TUNE },
|
|
+ {{ 0x0043, 0x0043, 0x0043, 0x0042, 0x0043, 0x0043, 0x0042 }, CMN_DIAG_PLL0_CP_TUNE },
|
|
+ {{ 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008 }, CMN_DIAG_PLL0_LF_PROG },
|
|
+ {{ 0x0100, 0x0001, 0x0001, 0x0001, 0x0100, 0x0001, 0x0001 }, CMN_DIAG_PLL0_PTATIS_TUNE1 },
|
|
+ {{ 0x0007, 0x0001, 0x0001, 0x0001, 0x0007, 0x0001, 0x0001 }, CMN_DIAG_PLL0_PTATIS_TUNE2 },
|
|
+ {{ 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020 }, CMN_DIAG_PLL0_TEST_MODE},
|
|
+ {{ 0x0016, 0x0016, 0x0016, 0x0016, 0x0016, 0x0016, 0x0016 }, CMN_PSM_CLK_CTRL }
|
|
+};
|
|
+
|
|
+static int dp_link_rate_index(u32 rate)
|
|
+{
|
|
+ switch (rate) {
|
|
+ case 162000:
|
|
+ return 0;
|
|
+ case 216000:
|
|
+ return 1;
|
|
+ case 243000:
|
|
+ return 2;
|
|
+ case 270000:
|
|
+ return 3;
|
|
+ case 324000:
|
|
+ return 4;
|
|
+ case 432000:
|
|
+ return 5;
|
|
+ case 540000:
|
|
+ return 6;
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+}
|
|
+
|
|
+static int cdns_phy_reg_write(struct cdns_hdptx_phy *cdns_phy, u32 addr, u32 val)
|
|
+{
|
|
+ return cdns_mhdp_reg_write(&cdns_phy->base, ADDR_PHY_AFE + (addr << 2), val);
|
|
+}
|
|
+
|
|
+static u32 cdns_phy_reg_read(struct cdns_hdptx_phy *cdns_phy, u32 addr)
|
|
+{
|
|
+ u32 reg32;
|
|
+
|
|
+ cdns_mhdp_reg_read(&cdns_phy->base, ADDR_PHY_AFE + (addr << 2), ®32);
|
|
+
|
|
+ return reg32;
|
|
+}
|
|
+
|
|
+static void hdptx_dp_aux_cfg(struct cdns_hdptx_phy *cdns_phy)
|
|
+{
|
|
+ /* Power up Aux */
|
|
+ cdns_phy_reg_write(cdns_phy, TXDA_CYA_AUXDA_CYA, 1);
|
|
+
|
|
+ cdns_phy_reg_write(cdns_phy, TX_DIG_CTRL_REG_1, 0x3);
|
|
+ ndelay(150);
|
|
+ cdns_phy_reg_write(cdns_phy, TX_DIG_CTRL_REG_2, 36);
|
|
+ ndelay(150);
|
|
+ cdns_phy_reg_write(cdns_phy, TX_ANA_CTRL_REG_2, 0x0100);
|
|
+ ndelay(150);
|
|
+ cdns_phy_reg_write(cdns_phy, TX_ANA_CTRL_REG_2, 0x0300);
|
|
+ ndelay(150);
|
|
+ cdns_phy_reg_write(cdns_phy, TX_ANA_CTRL_REG_3, 0x0000);
|
|
+ ndelay(150);
|
|
+ cdns_phy_reg_write(cdns_phy, TX_ANA_CTRL_REG_1, 0x2008);
|
|
+ ndelay(150);
|
|
+ cdns_phy_reg_write(cdns_phy, TX_ANA_CTRL_REG_1, 0x2018);
|
|
+ ndelay(150);
|
|
+ cdns_phy_reg_write(cdns_phy, TX_ANA_CTRL_REG_1, 0xa018);
|
|
+ ndelay(150);
|
|
+ cdns_phy_reg_write(cdns_phy, TX_ANA_CTRL_REG_2, 0x030c);
|
|
+ ndelay(150);
|
|
+ cdns_phy_reg_write(cdns_phy, TX_ANA_CTRL_REG_5, 0x0000);
|
|
+ ndelay(150);
|
|
+ cdns_phy_reg_write(cdns_phy, TX_ANA_CTRL_REG_4, 0x1001);
|
|
+ ndelay(150);
|
|
+ cdns_phy_reg_write(cdns_phy, TX_ANA_CTRL_REG_1, 0xa098);
|
|
+ ndelay(150);
|
|
+ cdns_phy_reg_write(cdns_phy, TX_ANA_CTRL_REG_1, 0xa198);
|
|
+ ndelay(150);
|
|
+ cdns_phy_reg_write(cdns_phy, TX_ANA_CTRL_REG_2, 0x030d);
|
|
+ ndelay(150);
|
|
+ cdns_phy_reg_write(cdns_phy, TX_ANA_CTRL_REG_2, 0x030f);
|
|
+}
|
|
+
|
|
+/* PMA common configuration for 27MHz */
|
|
+static void hdptx_dp_phy_pma_cmn_cfg_27mhz(struct cdns_hdptx_phy *cdns_phy)
|
|
+{
|
|
+ u32 num_lanes = cdns_phy->dp.lanes;
|
|
+ u16 val;
|
|
+ int k;
|
|
+
|
|
+ /* Enable PMA input ref clk(CMN_REF_CLK_RCV_EN) */
|
|
+ val = cdns_phy_reg_read(cdns_phy, PHY_PMA_CMN_CTRL1);
|
|
+ val &= ~CMA_REF_CLK_RCV_EN_MASK;
|
|
+ val |= FIELD_PREP(CMA_REF_CLK_RCV_EN_MASK, CMA_REF_CLK_RCV_EN);
|
|
+ cdns_phy_reg_write(cdns_phy, PHY_PMA_CMN_CTRL1, val);
|
|
+
|
|
+ /* Startup state machine registers */
|
|
+ cdns_phy_reg_write(cdns_phy, CMN_SSM_BIAS_TMR, 0x0087);
|
|
+ cdns_phy_reg_write(cdns_phy, CMN_PLLSM0_PLLEN_TMR, 0x001b);
|
|
+ cdns_phy_reg_write(cdns_phy, CMN_PLLSM0_PLLPRE_TMR, 0x0036);
|
|
+ cdns_phy_reg_write(cdns_phy, CMN_PLLSM0_PLLVREF_TMR, 0x001b);
|
|
+ cdns_phy_reg_write(cdns_phy, CMN_PLLSM0_PLLLOCK_TMR, 0x006c);
|
|
+
|
|
+ /* Current calibration registers */
|
|
+ cdns_phy_reg_write(cdns_phy, CMN_ICAL_INIT_TMR, 0x0044);
|
|
+ cdns_phy_reg_write(cdns_phy, CMN_ICAL_ITER_TMR, 0x0006);
|
|
+ cdns_phy_reg_write(cdns_phy, CMN_ICAL_ADJ_INIT_TMR, 0x0022);
|
|
+ cdns_phy_reg_write(cdns_phy, CMN_ICAL_ADJ_ITER_TMR, 0x0006);
|
|
+
|
|
+ /* Resistor calibration registers */
|
|
+ cdns_phy_reg_write(cdns_phy, CMN_TXPUCAL_INIT_TMR, 0x0022);
|
|
+ cdns_phy_reg_write(cdns_phy, CMN_TXPUCAL_ITER_TMR, 0x0006);
|
|
+ cdns_phy_reg_write(cdns_phy, CMN_TXPU_ADJ_INIT_TMR, 0x0022);
|
|
+ cdns_phy_reg_write(cdns_phy, CMN_TXPU_ADJ_ITER_TMR, 0x0006);
|
|
+ cdns_phy_reg_write(cdns_phy, CMN_TXPDCAL_INIT_TMR, 0x0022);
|
|
+ cdns_phy_reg_write(cdns_phy, CMN_TXPDCAL_ITER_TMR, 0x0006);
|
|
+ cdns_phy_reg_write(cdns_phy, CMN_TXPD_ADJ_INIT_TMR, 0x0022);
|
|
+ cdns_phy_reg_write(cdns_phy, CMN_TXPD_ADJ_ITER_TMR, 0x0006);
|
|
+ cdns_phy_reg_write(cdns_phy, CMN_RXCAL_INIT_TMR, 0x0022);
|
|
+ cdns_phy_reg_write(cdns_phy, CMN_RXCAL_ITER_TMR, 0x0006);
|
|
+ cdns_phy_reg_write(cdns_phy, CMN_RX_ADJ_INIT_TMR, 0x0022);
|
|
+ cdns_phy_reg_write(cdns_phy, CMN_RX_ADJ_ITER_TMR, 0x0006);
|
|
+
|
|
+ for (k = 0; k < num_lanes; k = k + 1) {
|
|
+ /* Power state machine registers */
|
|
+ cdns_phy_reg_write(cdns_phy, XCVR_PSM_CAL_TMR | (k << 9), 0x016d);
|
|
+ cdns_phy_reg_write(cdns_phy, XCVR_PSM_A0IN_TMR | (k << 9), 0x016d);
|
|
+ /* Transceiver control and diagnostic registers */
|
|
+ cdns_phy_reg_write(cdns_phy, XCVR_DIAG_LANE_FCM_EN_MGN_TMR | (k << 9), 0x00a2);
|
|
+ cdns_phy_reg_write(cdns_phy, TX_DIAG_BGREF_PREDRV_DELAY | (k << 9), 0x0097);
|
|
+ /* Transmitter receiver detect registers */
|
|
+ cdns_phy_reg_write(cdns_phy, TX_RCVDET_EN_TMR | (k << 9), 0x0a8c);
|
|
+ cdns_phy_reg_write(cdns_phy, TX_RCVDET_ST_TMR | (k << 9), 0x0036);
|
|
+ }
|
|
+
|
|
+ cdns_phy_reg_write(cdns_phy, TX_DIAG_ACYA_0, 1);
|
|
+ cdns_phy_reg_write(cdns_phy, TX_DIAG_ACYA_1, 1);
|
|
+ cdns_phy_reg_write(cdns_phy, TX_DIAG_ACYA_2, 1);
|
|
+ cdns_phy_reg_write(cdns_phy, TX_DIAG_ACYA_3, 1);
|
|
+}
|
|
+
|
|
+static void hdptx_dp_phy_pma_cmn_pll0_27mhz(struct cdns_hdptx_phy *cdns_phy)
|
|
+{
|
|
+ u32 num_lanes = cdns_phy->dp.lanes;
|
|
+ u32 link_rate = cdns_phy->dp.link_rate;
|
|
+ u16 val;
|
|
+ int index, i, k;
|
|
+
|
|
+ /* DP PLL data rate 0/1 clock divider value */
|
|
+ val = cdns_phy_reg_read(cdns_phy, PHY_HDP_CLK_CTL);
|
|
+ val &= ~PLL_DATA_RATE_CLK_DIV_MASK;
|
|
+ if (link_rate <= LINK_RATE_2_7)
|
|
+ val |= FIELD_PREP(PLL_DATA_RATE_CLK_DIV_MASK,
|
|
+ PLL_DATA_RATE_CLK_DIV_HBR);
|
|
+ else
|
|
+ val |= FIELD_PREP(PLL_DATA_RATE_CLK_DIV_MASK,
|
|
+ PLL_DATA_RATE_CLK_DIV_HBR2);
|
|
+ cdns_phy_reg_write(cdns_phy, PHY_HDP_CLK_CTL, val);
|
|
+
|
|
+ /* High speed clock 0/1 div */
|
|
+ val = cdns_phy_reg_read(cdns_phy, CMN_DIAG_HSCLK_SEL);
|
|
+ val &= ~(HSCLK1_SEL_MASK | HSCLK0_SEL_MASK);
|
|
+ if (link_rate <= LINK_RATE_2_7) {
|
|
+ val |= FIELD_PREP(HSCLK1_SEL_MASK, HSCLK_PLL0_DIV2);
|
|
+ val |= FIELD_PREP(HSCLK0_SEL_MASK, HSCLK_PLL0_DIV2);
|
|
+ }
|
|
+ cdns_phy_reg_write(cdns_phy, CMN_DIAG_HSCLK_SEL, val);
|
|
+
|
|
+ for (k = 0; k < num_lanes; k++) {
|
|
+ val = cdns_phy_reg_read(cdns_phy, (XCVR_DIAG_HSCLK_SEL | (k << 9)));
|
|
+ val &= ~HSCLK_SEL_MODE3_MASK;
|
|
+ if (link_rate <= LINK_RATE_2_7)
|
|
+ val |= FIELD_PREP(HSCLK_SEL_MODE3_MASK, HSCLK_SEL_MODE3_HSCLK1);
|
|
+ cdns_phy_reg_write(cdns_phy, (XCVR_DIAG_HSCLK_SEL | (k << 9)), val);
|
|
+ }
|
|
+
|
|
+ /* DP PHY PLL 27MHz configuration */
|
|
+ index = dp_link_rate_index(link_rate);
|
|
+ if (index < 0) {
|
|
+ dev_err(cdns_phy->dev, "Not support link rate %d\n", link_rate);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ for (i = 0; i < ARRAY_SIZE(phy_pll_27m_cfg); i++)
|
|
+ cdns_phy_reg_write(cdns_phy, phy_pll_27m_cfg[i].addr,
|
|
+ phy_pll_27m_cfg[i].val[index]);
|
|
+
|
|
+ /* Transceiver control and diagnostic registers */
|
|
+ for (k = 0; k < num_lanes; k++) {
|
|
+ val = cdns_phy_reg_read(cdns_phy, (XCVR_DIAG_PLLDRC_CTRL | (k << 9)));
|
|
+ val &= ~(DPLL_DATA_RATE_DIV_MODE3_MASK | DPLL_CLK_SEL_MODE3);
|
|
+ if (link_rate <= LINK_RATE_2_7)
|
|
+ val |= FIELD_PREP(DPLL_DATA_RATE_DIV_MODE3_MASK, 2);
|
|
+ else
|
|
+ val |= FIELD_PREP(DPLL_DATA_RATE_DIV_MODE3_MASK, 1);
|
|
+ cdns_phy_reg_write(cdns_phy, (XCVR_DIAG_PLLDRC_CTRL | (k << 9)), val);
|
|
+ }
|
|
+
|
|
+ for (k = 0; k < num_lanes; k = k + 1) {
|
|
+ /* Power state machine registers */
|
|
+ cdns_phy_reg_write(cdns_phy, (XCVR_PSM_RCTRL | (k << 9)), 0xbefc);
|
|
+ cdns_phy_reg_write(cdns_phy, (TX_PSC_A0 | (k << 9)), 0x6799);
|
|
+ cdns_phy_reg_write(cdns_phy, (TX_PSC_A1 | (k << 9)), 0x6798);
|
|
+ cdns_phy_reg_write(cdns_phy, (TX_PSC_A2 | (k << 9)), 0x0098);
|
|
+ cdns_phy_reg_write(cdns_phy, (TX_PSC_A3 | (k << 9)), 0x0098);
|
|
+ /* Receiver calibration power state definition register */
|
|
+ val = cdns_phy_reg_read(cdns_phy, RX_PSC_CAL | (k << 9));
|
|
+ val &= 0xffbb;
|
|
+ cdns_phy_reg_write(cdns_phy, (RX_PSC_CAL | (k << 9)), val);
|
|
+ val = cdns_phy_reg_read(cdns_phy, RX_PSC_A0 | (k << 9));
|
|
+ val &= 0xffbb;
|
|
+ cdns_phy_reg_write(cdns_phy, (RX_PSC_A0 | (k << 9)), val);
|
|
+ }
|
|
+}
|
|
+
|
|
+static void hdptx_dp_phy_ref_clock_type(struct cdns_hdptx_phy *cdns_phy)
|
|
+{
|
|
+ u32 val;
|
|
+
|
|
+ val = cdns_phy_reg_read(cdns_phy, PHY_PMA_CMN_CTRL1);
|
|
+ val &= ~CMA_REF_CLK_SEL_MASK;
|
|
+ /*
|
|
+ * single ended reference clock (val |= 0x0030);
|
|
+ * differential clock (val |= 0x0000);
|
|
+ *
|
|
+ * for differential clock on the refclk_p and
|
|
+ * refclk_m off chip pins: CMN_DIAG_ACYA[8]=1'b1
|
|
+ * cdns_phy_reg_write(cdns_phy, CMN_DIAG_ACYA, 0x0100);
|
|
+ */
|
|
+ val |= FIELD_PREP(CMA_REF_CLK_SEL_MASK, 3);
|
|
+ cdns_phy_reg_write(cdns_phy, PHY_PMA_CMN_CTRL1, val);
|
|
+}
|
|
+
|
|
+static int wait_for_ack(struct cdns_hdptx_phy *cdns_phy,
|
|
+ u32 reg, u32 mask,
|
|
+ const char *err_msg)
|
|
+{
|
|
+ int ret;
|
|
+ u32 val;
|
|
+
|
|
+ ret = read_poll_timeout(cdns_phy_reg_read,
|
|
+ val, val & mask, 20, 1000,
|
|
+ false, cdns_phy, reg);
|
|
+ if (ret < 0)
|
|
+ dev_err(cdns_phy->dev, "%s\n", err_msg);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int wait_for_ack_clear(struct cdns_hdptx_phy *cdns_phy,
|
|
+ u32 reg, u32 mask,
|
|
+ const char *err_msg)
|
|
+{
|
|
+ int ret;
|
|
+ u32 val;
|
|
+
|
|
+ ret = read_poll_timeout(cdns_phy_reg_read,
|
|
+ val, !(val & mask), 20, 1000,
|
|
+ false, cdns_phy, reg);
|
|
+ if (ret < 0)
|
|
+ dev_err(cdns_phy->dev, "%s\n", err_msg);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int hdptx_dp_phy_power_up(struct cdns_hdptx_phy *cdns_phy)
|
|
+{
|
|
+ u32 val;
|
|
+ int ret;
|
|
+
|
|
+ /* Enable HDP PLL's for high speed clocks */
|
|
+ val = cdns_phy_reg_read(cdns_phy, PHY_HDP_CLK_CTL);
|
|
+ val |= PLL_EN;
|
|
+ cdns_phy_reg_write(cdns_phy, PHY_HDP_CLK_CTL, val);
|
|
+ ret = wait_for_ack(cdns_phy, PHY_HDP_CLK_CTL, PLL_READY,
|
|
+ "Wait PLL Ack failed");
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ /* Enable HDP PLL's data rate and full rate clocks out of PMA. */
|
|
+ val = cdns_phy_reg_read(cdns_phy, PHY_HDP_CLK_CTL);
|
|
+ val |= PLL_CLK_EN;
|
|
+ cdns_phy_reg_write(cdns_phy, PHY_HDP_CLK_CTL, val);
|
|
+ ret = wait_for_ack(cdns_phy, PHY_HDP_CLK_CTL, PLL_CLK_EN_ACK,
|
|
+ "Wait PLL clock enable ACK failed");
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ /* Configure PHY in A2 Mode */
|
|
+ cdns_phy_reg_write(cdns_phy, PHY_HDP_MODE_CTRL, POWER_STATE_A2);
|
|
+ ret = wait_for_ack(cdns_phy, PHY_HDP_MODE_CTRL, POWER_STATE_A2_ACK,
|
|
+ "Wait A2 Ack failed");
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ /* Configure PHY in A0 mode (PHY must be in the A0 power
|
|
+ * state in order to transmit data)
|
|
+ */
|
|
+ cdns_phy_reg_write(cdns_phy, PHY_HDP_MODE_CTRL, POWER_STATE_A0);
|
|
+
|
|
+ return wait_for_ack(cdns_phy, PHY_HDP_MODE_CTRL, POWER_STATE_A0_ACK,
|
|
+ "Wait A0 Ack failed");
|
|
+}
|
|
+
|
|
+static int hdptx_dp_phy_power_down(struct cdns_hdptx_phy *cdns_phy)
|
|
+{
|
|
+ u16 val;
|
|
+ int ret;
|
|
+
|
|
+ /* Place the PHY lanes in the A3 power state. */
|
|
+ cdns_phy_reg_write(cdns_phy, PHY_HDP_MODE_CTRL, POWER_STATE_A3);
|
|
+ ret = wait_for_ack(cdns_phy, PHY_HDP_MODE_CTRL, POWER_STATE_A3_ACK,
|
|
+ "Wait A3 Ack failed");
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ /* Disable HDP PLL's data rate and full rate clocks out of PMA. */
|
|
+ val = cdns_phy_reg_read(cdns_phy, PHY_HDP_CLK_CTL);
|
|
+ val &= ~PLL_CLK_EN;
|
|
+ cdns_phy_reg_write(cdns_phy, PHY_HDP_CLK_CTL, val);
|
|
+ ret = wait_for_ack_clear(cdns_phy, PHY_HDP_CLK_CTL, PLL_CLK_EN_ACK,
|
|
+ "Wait PLL clock Ack clear failed");
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ /* Disable HDP PLL's for high speed clocks */
|
|
+ val = cdns_phy_reg_read(cdns_phy, PHY_HDP_CLK_CTL);
|
|
+ val &= ~PLL_EN;
|
|
+ cdns_phy_reg_write(cdns_phy, PHY_HDP_CLK_CTL, val);
|
|
+
|
|
+ return wait_for_ack_clear(cdns_phy, PHY_HDP_CLK_CTL, PLL_READY,
|
|
+ "Wait PLL Ack clear failed");
|
|
+}
|
|
+
|
|
+static int hdptx_dp_configure(struct phy *phy,
|
|
+ union phy_configure_opts *opts)
|
|
+{
|
|
+ const struct phy_configure_opts_dp *dp_opts = &opts->dp;
|
|
+ struct cdns_hdptx_phy *cdns_phy = phy_get_drvdata(phy);
|
|
+
|
|
+ if (opts->dp.link_rate > MAX_LINK_RATE) {
|
|
+ dev_err(cdns_phy->dev, "Link Rate(%d) Not supported\n", opts->dp.link_rate);
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ memcpy(&cdns_phy->dp, dp_opts, sizeof(*dp_opts));
|
|
+
|
|
+ hdptx_dp_phy_pma_cmn_cfg_27mhz(cdns_phy);
|
|
+ hdptx_dp_phy_pma_cmn_pll0_27mhz(cdns_phy);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int hdptx_clk_enable(struct cdns_hdptx_phy *cdns_phy)
|
|
+{
|
|
+ struct device *dev = cdns_phy->dev;
|
|
+ u32 ref_clk_rate;
|
|
+
|
|
+ cdns_phy->ref_clk = devm_clk_get_enabled(dev, "ref");
|
|
+ if (IS_ERR(cdns_phy->ref_clk)) {
|
|
+ dev_err(dev, "phy ref clock not found\n");
|
|
+ return PTR_ERR(cdns_phy->ref_clk);
|
|
+ }
|
|
+
|
|
+ ref_clk_rate = clk_get_rate(cdns_phy->ref_clk);
|
|
+ if (!ref_clk_rate) {
|
|
+ dev_err(cdns_phy->dev, "Failed to get ref clock rate\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ if (ref_clk_rate == REF_CLK_27MHZ) {
|
|
+ cdns_phy->ref_clk_rate = ref_clk_rate;
|
|
+ } else {
|
|
+ dev_err(cdns_phy->dev, "Not support Ref Clock Rate(%dHz)\n", ref_clk_rate);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ cdns_phy->apb_clk = devm_clk_get_enabled(dev, "apb");
|
|
+ if (IS_ERR(cdns_phy->apb_clk)) {
|
|
+ dev_err(dev, "phy apb clock not found\n");
|
|
+ return PTR_ERR(cdns_phy->apb_clk);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void hdptx_hdmi_arc_config(struct cdns_hdptx_phy *cdns_phy)
|
|
+{
|
|
+ u16 txpu_calib_code;
|
|
+ u16 txpd_calib_code;
|
|
+ u16 txpu_adj_calib_code;
|
|
+ u16 txpd_adj_calib_code;
|
|
+ u16 prev_calib_code;
|
|
+ u16 new_calib_code;
|
|
+ u16 rdata;
|
|
+
|
|
+ /* Power ARC */
|
|
+ cdns_phy_reg_write(cdns_phy, TXDA_CYA_AUXDA_CYA, 0x0001);
|
|
+
|
|
+ prev_calib_code = cdns_phy_reg_read(cdns_phy, TX_DIG_CTRL_REG_2);
|
|
+ txpu_calib_code = cdns_phy_reg_read(cdns_phy, CMN_TXPUCAL_CTRL);
|
|
+ txpd_calib_code = cdns_phy_reg_read(cdns_phy, CMN_TXPDCAL_CTRL);
|
|
+ txpu_adj_calib_code = cdns_phy_reg_read(cdns_phy, CMN_TXPU_ADJ_CTRL);
|
|
+ txpd_adj_calib_code = cdns_phy_reg_read(cdns_phy, CMN_TXPD_ADJ_CTRL);
|
|
+
|
|
+ new_calib_code = ((txpu_calib_code + txpd_calib_code) / 2)
|
|
+ + txpu_adj_calib_code + txpd_adj_calib_code;
|
|
+
|
|
+ if (new_calib_code != prev_calib_code) {
|
|
+ rdata = cdns_phy_reg_read(cdns_phy, TX_ANA_CTRL_REG_1);
|
|
+ rdata &= 0xdfff;
|
|
+ cdns_phy_reg_write(cdns_phy, TX_ANA_CTRL_REG_1, rdata);
|
|
+ cdns_phy_reg_write(cdns_phy, TX_DIG_CTRL_REG_2, new_calib_code);
|
|
+ mdelay(10);
|
|
+ rdata |= 0x2000;
|
|
+ cdns_phy_reg_write(cdns_phy, TX_ANA_CTRL_REG_1, rdata);
|
|
+ usleep_range(150, 250);
|
|
+ }
|
|
+
|
|
+ cdns_phy_reg_write(cdns_phy, TX_ANA_CTRL_REG_2, 0x0100);
|
|
+ usleep_range(100, 200);
|
|
+ cdns_phy_reg_write(cdns_phy, TX_ANA_CTRL_REG_2, 0x0300);
|
|
+ usleep_range(100, 200);
|
|
+ cdns_phy_reg_write(cdns_phy, TX_ANA_CTRL_REG_3, 0x0000);
|
|
+ usleep_range(100, 200);
|
|
+ cdns_phy_reg_write(cdns_phy, TX_ANA_CTRL_REG_1, 0x2008);
|
|
+ usleep_range(100, 200);
|
|
+ cdns_phy_reg_write(cdns_phy, TX_ANA_CTRL_REG_1, 0x2018);
|
|
+ usleep_range(100, 200);
|
|
+ cdns_phy_reg_write(cdns_phy, TX_ANA_CTRL_REG_1, 0x2098);
|
|
+ cdns_phy_reg_write(cdns_phy, TX_ANA_CTRL_REG_2, 0x030c);
|
|
+ cdns_phy_reg_write(cdns_phy, TX_ANA_CTRL_REG_5, 0x0010);
|
|
+ usleep_range(100, 200);
|
|
+ cdns_phy_reg_write(cdns_phy, TX_ANA_CTRL_REG_4, 0x4001);
|
|
+ mdelay(5);
|
|
+ cdns_phy_reg_write(cdns_phy, TX_ANA_CTRL_REG_1, 0x2198);
|
|
+ mdelay(5);
|
|
+ cdns_phy_reg_write(cdns_phy, TX_ANA_CTRL_REG_2, 0x030d);
|
|
+ usleep_range(100, 200);
|
|
+ cdns_phy_reg_write(cdns_phy, TX_ANA_CTRL_REG_2, 0x030f);
|
|
+}
|
|
+
|
|
+static void hdptx_hdmi_phy_set_vswing(struct cdns_hdptx_phy *cdns_phy)
|
|
+{
|
|
+ u32 k;
|
|
+ const u32 num_lanes = 4;
|
|
+
|
|
+ for (k = 0; k < num_lanes; k++) {
|
|
+ cdns_phy_reg_write(cdns_phy, (TX_DIAG_TX_DRV | (k << 9)),
|
|
+ TX_DRIVER_PROG_BOOST_ENABLE |
|
|
+ FIELD_PREP(TX_DRIVER_PROG_BOOST_LEVEL_MASK, 3) |
|
|
+ TX_DRIVER_LDO_BG_DEPENDENT_REF_ENABLE |
|
|
+ TX_DRIVER_LDO_BANDGAP_REF_ENABLE);
|
|
+ cdns_phy_reg_write(cdns_phy, (TX_TXCC_CPOST_MULT_00_0 | (k << 9)), 0x0);
|
|
+ cdns_phy_reg_write(cdns_phy, (TX_TXCC_CAL_SCLR_MULT_0 | (k << 9)),
|
|
+ SCALED_RESISTOR_CALIBRATION_CODE_ADD |
|
|
+ RESISTOR_CAL_MULT_VAL_32_128);
|
|
+ }
|
|
+}
|
|
+
|
|
+static int hdptx_hdmi_phy_config(struct cdns_hdptx_phy *cdns_phy,
|
|
+ const struct hdptx_hdmi_ctrl *p_ctrl_table,
|
|
+ const struct hdptx_hdmi_pll_tuning *p_pll_table,
|
|
+ bool pclk_in)
|
|
+{
|
|
+ const u32 num_lanes = 4;
|
|
+ u32 val, k;
|
|
+ int ret;
|
|
+
|
|
+ /* enable PHY isolation mode only for CMN */
|
|
+ cdns_phy_reg_write(cdns_phy, PHY_PMA_ISOLATION_CTRL, 0xd000);
|
|
+
|
|
+ /* set cmn_pll0_clk_datart1_div/cmn_pll0_clk_datart0_div dividers */
|
|
+ val = cdns_phy_reg_read(cdns_phy, PHY_PMA_ISO_PLL_CTRL1);
|
|
+ val &= ~CMN_PLL0_CLK_DATART_DIV_MASK;
|
|
+ val |= FIELD_PREP(CMN_PLL0_CLK_DATART_DIV_MASK, 0x12);
|
|
+ cdns_phy_reg_write(cdns_phy, PHY_PMA_ISO_PLL_CTRL1, val);
|
|
+
|
|
+ /* assert PHY reset from isolation register */
|
|
+ cdns_phy_reg_write(cdns_phy, PHY_ISO_CMN_CTRL, 0x0000);
|
|
+ /* assert PMA CMN reset */
|
|
+ cdns_phy_reg_write(cdns_phy, PHY_PMA_ISO_CMN_CTRL, 0x0000);
|
|
+
|
|
+ /* register XCVR_DIAG_BIDI_CTRL */
|
|
+ for (k = 0; k < num_lanes; k++)
|
|
+ cdns_phy_reg_write(cdns_phy, XCVR_DIAG_BIDI_CTRL | (k << 9), 0x00ff);
|
|
+
|
|
+ /* Describing Task phy_cfg_hdp */
|
|
+ val = cdns_phy_reg_read(cdns_phy, PHY_PMA_CMN_CTRL1);
|
|
+ val &= ~CMA_REF_CLK_RCV_EN_MASK;
|
|
+ val |= FIELD_PREP(CMA_REF_CLK_RCV_EN_MASK, CMA_REF_CLK_RCV_EN);
|
|
+ cdns_phy_reg_write(cdns_phy, PHY_PMA_CMN_CTRL1, val);
|
|
+
|
|
+ /* PHY Registers */
|
|
+ val = cdns_phy_reg_read(cdns_phy, PHY_PMA_CMN_CTRL1);
|
|
+ val &= ~CMA_REF_CLK_DIG_DIV_MASK;
|
|
+ val |= FIELD_PREP(CMA_REF_CLK_DIG_DIV_MASK, CMN_REF_CLK_DIG_DIV);
|
|
+ cdns_phy_reg_write(cdns_phy, PHY_PMA_CMN_CTRL1, val);
|
|
+
|
|
+ val = cdns_phy_reg_read(cdns_phy, PHY_HDP_CLK_CTL);
|
|
+ val &= ~PLL_DATA_RATE_CLK_DIV_MASK;
|
|
+ val |= FIELD_PREP(PLL_DATA_RATE_CLK_DIV_MASK,
|
|
+ PLL_DATA_RATE_CLK_DIV_HBR2);
|
|
+ cdns_phy_reg_write(cdns_phy, PHY_HDP_CLK_CTL, val);
|
|
+
|
|
+ /* Common control module control and diagnostic registers */
|
|
+ val = cdns_phy_reg_read(cdns_phy, CMN_CDIAG_REFCLK_CTRL);
|
|
+ val &= ~DIG_REF_CLK_DIV_SCALER_MASK;
|
|
+ val |= FIELD_PREP(DIG_REF_CLK_DIV_SCALER_MASK, REF_CLK_DIVIDER_SCALER);
|
|
+ val |= REFCLK_TERMINATION_EN_OVERRIDE_EN | REFCLK_TERMINATION_EN_OVERRIDE;
|
|
+ cdns_phy_reg_write(cdns_phy, CMN_CDIAG_REFCLK_CTRL, val);
|
|
+
|
|
+ /* High speed clock used */
|
|
+ val = cdns_phy_reg_read(cdns_phy, CMN_DIAG_HSCLK_SEL);
|
|
+ val &= ~(HSCLK1_SEL_MASK | HSCLK0_SEL_MASK);
|
|
+ val |= FIELD_PREP(HSCLK1_SEL_MASK, (p_ctrl_table->cmnda_hs_clk_1_sel >> 1));
|
|
+ val |= FIELD_PREP(HSCLK0_SEL_MASK, (p_ctrl_table->cmnda_hs_clk_0_sel >> 1));
|
|
+ cdns_phy_reg_write(cdns_phy, CMN_DIAG_HSCLK_SEL, val);
|
|
+
|
|
+ for (k = 0; k < num_lanes; k++) {
|
|
+ val = cdns_phy_reg_read(cdns_phy, (XCVR_DIAG_HSCLK_SEL | (k << 9)));
|
|
+ val &= ~HSCLK_SEL_MODE3_MASK;
|
|
+ val |= FIELD_PREP(HSCLK_SEL_MODE3_MASK,
|
|
+ (p_ctrl_table->cmnda_hs_clk_0_sel >> 1));
|
|
+ cdns_phy_reg_write(cdns_phy, (XCVR_DIAG_HSCLK_SEL | (k << 9)), val);
|
|
+ }
|
|
+
|
|
+ /* PLL 0 control state machine registers */
|
|
+ val = p_ctrl_table->vco_ring_select << 12;
|
|
+ cdns_phy_reg_write(cdns_phy, CMN_PLLSM0_USER_DEF_CTRL, val);
|
|
+
|
|
+ if (pclk_in) {
|
|
+ val = 0x30a0;
|
|
+ } else {
|
|
+ val = cdns_phy_reg_read(cdns_phy, CMN_PLL0_VCOCAL_START);
|
|
+ val &= ~VCO_CALIB_CODE_START_POINT_VAL_MASK;
|
|
+ val |= FIELD_PREP(VCO_CALIB_CODE_START_POINT_VAL_MASK,
|
|
+ p_pll_table->vco_cal_code);
|
|
+ }
|
|
+ cdns_phy_reg_write(cdns_phy, CMN_PLL0_VCOCAL_START, val);
|
|
+
|
|
+ cdns_phy_reg_write(cdns_phy, CMN_PLL0_VCOCAL_INIT_TMR, 0x0064);
|
|
+ cdns_phy_reg_write(cdns_phy, CMN_PLL0_VCOCAL_ITER_TMR, 0x000a);
|
|
+
|
|
+ /* Common functions control and diagnostics registers */
|
|
+ val = p_ctrl_table->cmnda_pll0_hs_sym_div_sel << 8;
|
|
+ val |= p_ctrl_table->cmnda_pll0_ip_div;
|
|
+ cdns_phy_reg_write(cdns_phy, CMN_DIAG_PLL0_INCLK_CTRL, val);
|
|
+
|
|
+ cdns_phy_reg_write(cdns_phy, CMN_DIAG_PLL0_OVRD, 0x0000);
|
|
+
|
|
+ val = p_ctrl_table->cmnda_pll0_fb_div_high;
|
|
+ val |= PLL_FEEDBACK_DIV_HI_OVERRIDE_EN;
|
|
+ cdns_phy_reg_write(cdns_phy, CMN_DIAG_PLL0_FBH_OVRD, val);
|
|
+
|
|
+ val = p_ctrl_table->cmnda_pll0_fb_div_low;
|
|
+ val |= PLL_FEEDBACK_DIV_LO_OVERRIDE_EN;
|
|
+ cdns_phy_reg_write(cdns_phy, CMN_DIAG_PLL0_FBL_OVRD, val);
|
|
+
|
|
+ if (!pclk_in) {
|
|
+ val = p_ctrl_table->cmnda_pll0_pxdiv_low;
|
|
+ cdns_phy_reg_write(cdns_phy, CMN_DIAG_PLL0_PXL_DIVL, val);
|
|
+
|
|
+ val = p_ctrl_table->cmnda_pll0_pxdiv_high;
|
|
+ val |= PLL_PCLK_DIV_EN;
|
|
+ cdns_phy_reg_write(cdns_phy, CMN_DIAG_PLL0_PXL_DIVH, val);
|
|
+ }
|
|
+
|
|
+ val = p_pll_table->volt_to_current_coarse;
|
|
+ val |= (p_pll_table->volt_to_current) << 4;
|
|
+ cdns_phy_reg_write(cdns_phy, CMN_DIAG_PLL0_V2I_TUNE, val);
|
|
+
|
|
+ val = p_pll_table->charge_pump_gain;
|
|
+ cdns_phy_reg_write(cdns_phy, CMN_DIAG_PLL0_CP_TUNE, val);
|
|
+
|
|
+ cdns_phy_reg_write(cdns_phy, CMN_DIAG_PLL0_LF_PROG, 0x0008);
|
|
+
|
|
+ val = p_pll_table->pmos_ctrl;
|
|
+ val |= (p_pll_table->ndac_ctrl) << 8;
|
|
+ cdns_phy_reg_write(cdns_phy, CMN_DIAG_PLL0_PTATIS_TUNE1, val);
|
|
+
|
|
+ val = p_pll_table->ptat_ndac_ctrl;
|
|
+ cdns_phy_reg_write(cdns_phy, CMN_DIAG_PLL0_PTATIS_TUNE2, val);
|
|
+
|
|
+ if (pclk_in)
|
|
+ cdns_phy_reg_write(cdns_phy, CMN_DIAG_PLL0_TEST_MODE, 0x0022);
|
|
+ else
|
|
+ cdns_phy_reg_write(cdns_phy, CMN_DIAG_PLL0_TEST_MODE, 0x0020);
|
|
+
|
|
+ cdns_phy_reg_write(cdns_phy, CMN_PSM_CLK_CTRL, 0x0016);
|
|
+
|
|
+ /* Transceiver control and diagnostic registers */
|
|
+ for (k = 0; k < num_lanes; k++) {
|
|
+ val = cdns_phy_reg_read(cdns_phy, (XCVR_DIAG_PLLDRC_CTRL | (k << 9)));
|
|
+ val &= ~DPLL_CLK_SEL_MODE3;
|
|
+ cdns_phy_reg_write(cdns_phy, (XCVR_DIAG_PLLDRC_CTRL | (k << 9)), val);
|
|
+ }
|
|
+
|
|
+ for (k = 0; k < num_lanes; k++) {
|
|
+ val = cdns_phy_reg_read(cdns_phy, (TX_DIAG_TX_CTRL | (k << 9)));
|
|
+ val &= ~TX_IF_SUBRATE_MODE3_MASK;
|
|
+ val |= FIELD_PREP(TX_IF_SUBRATE_MODE3_MASK,
|
|
+ (p_ctrl_table->hsclk_div_tx_sub_rate >> 1));
|
|
+ cdns_phy_reg_write(cdns_phy, (TX_DIAG_TX_CTRL | (k << 9)), val);
|
|
+ }
|
|
+
|
|
+ val = cdns_phy_reg_read(cdns_phy, PHY_PMA_CMN_CTRL1);
|
|
+ val &= ~CMA_REF_CLK_SEL_MASK;
|
|
+ /*
|
|
+ * single ended reference clock (val |= 0x0030);
|
|
+ * differential clock (val |= 0x0000);
|
|
+ * for differential clock on the refclk_p and
|
|
+ * refclk_m off chip pins: CMN_DIAG_ACYA[8]=1'b1
|
|
+ * cdns_phy_reg_write(cdns_phy, CMN_DIAG_ACYA, 0x0100);
|
|
+ */
|
|
+ val |= FIELD_PREP(CMA_REF_CLK_SEL_MASK, 3);
|
|
+ cdns_phy_reg_write(cdns_phy, PHY_PMA_CMN_CTRL1, val);
|
|
+
|
|
+ /* Deassert PHY reset */
|
|
+ cdns_phy_reg_write(cdns_phy, PHY_ISO_CMN_CTRL, 0x0001);
|
|
+ cdns_phy_reg_write(cdns_phy, PHY_PMA_ISO_CMN_CTRL, 0x0003);
|
|
+
|
|
+ /* Power state machine registers */
|
|
+ for (k = 0; k < num_lanes; k++)
|
|
+ cdns_phy_reg_write(cdns_phy, XCVR_PSM_RCTRL | (k << 9), 0xfefc);
|
|
+
|
|
+ /* Assert cmn_macro_pwr_en */
|
|
+ cdns_phy_reg_write(cdns_phy, PHY_PMA_ISO_CMN_CTRL, 0x0013);
|
|
+
|
|
+ /* wait for cmn_macro_pwr_en_ack */
|
|
+ ret = wait_for_ack(cdns_phy, PHY_PMA_ISO_CMN_CTRL, CMN_MACRO_PWR_EN_ACK,
|
|
+ "MA output macro power up failed");
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ /* wait for cmn_ready */
|
|
+ ret = wait_for_ack(cdns_phy, PHY_PMA_CMN_CTRL1, CMN_READY,
|
|
+ "PMA output ready failed");
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ for (k = 0; k < num_lanes; k++) {
|
|
+ cdns_phy_reg_write(cdns_phy, TX_PSC_A0 | (k << 9), 0x6791);
|
|
+ cdns_phy_reg_write(cdns_phy, TX_PSC_A1 | (k << 9), 0x6790);
|
|
+ cdns_phy_reg_write(cdns_phy, TX_PSC_A2 | (k << 9), 0x0090);
|
|
+ cdns_phy_reg_write(cdns_phy, TX_PSC_A3 | (k << 9), 0x0090);
|
|
+
|
|
+ val = cdns_phy_reg_read(cdns_phy, RX_PSC_CAL | (k << 9));
|
|
+ val &= 0xffbb;
|
|
+ cdns_phy_reg_write(cdns_phy, RX_PSC_CAL | (k << 9), val);
|
|
+
|
|
+ val = cdns_phy_reg_read(cdns_phy, RX_PSC_A0 | (k << 9));
|
|
+ val &= 0xffbb;
|
|
+ cdns_phy_reg_write(cdns_phy, RX_PSC_A0 | (k << 9), val);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int hdptx_hdmi_phy_cfg(struct cdns_hdptx_phy *cdns_phy, unsigned long long char_rate)
|
|
+{
|
|
+ const struct hdptx_hdmi_ctrl *p_ctrl_table;
|
|
+ const struct hdptx_hdmi_pll_tuning *p_pll_table;
|
|
+ const u32 refclk_freq_khz = cdns_phy->ref_clk_rate / 1000;
|
|
+ const bool pclk_in = false;
|
|
+ u32 char_rate_khz = char_rate / 1000;
|
|
+ u32 vco_freq, rate;
|
|
+ u32 div_total, i;
|
|
+
|
|
+ dev_dbg(cdns_phy->dev, "character clock: %d KHz\n ", char_rate_khz);
|
|
+
|
|
+ /* Get right row from the ctrl_table table.
|
|
+ * check the character rate.
|
|
+ */
|
|
+ for (i = 0; i < ARRAY_SIZE(pixel_clk_output_ctrl_table); i++) {
|
|
+ rate = pixel_clk_output_ctrl_table[i].feedback_factor *
|
|
+ pixel_clk_output_ctrl_table[i].pixel_clk_freq / 1000;
|
|
+ if (char_rate_khz == rate) {
|
|
+ p_ctrl_table = &pixel_clk_output_ctrl_table[i];
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ if (i == ARRAY_SIZE(pixel_clk_output_ctrl_table)) {
|
|
+ dev_warn(cdns_phy->dev,
|
|
+ "char clk (%d KHz) not supported\n", char_rate_khz);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ div_total = p_ctrl_table->pll_fb_div_total;
|
|
+ vco_freq = refclk_freq_khz * div_total / p_ctrl_table->cmnda_pll0_ip_div;
|
|
+
|
|
+ /* Get right row from the pixel_clk_output_pll_table table.
|
|
+ * Check if vco_freq_khz and feedback_div_total
|
|
+ * column matching with pixel_clk_output_pll_table.
|
|
+ */
|
|
+ for (i = 0; i < ARRAY_SIZE(pixel_clk_output_pll_table); i++) {
|
|
+ if (vco_freq == pixel_clk_output_pll_table[i].vco_freq &&
|
|
+ div_total == pixel_clk_output_pll_table[i].feedback_div_total) {
|
|
+ p_pll_table = &pixel_clk_output_pll_table[i];
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ if (i == ARRAY_SIZE(pixel_clk_output_pll_table)) {
|
|
+ dev_warn(cdns_phy->dev, "VCO (%d KHz) not supported\n", vco_freq);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ dev_dbg(cdns_phy->dev, "VCO frequency is (%d KHz)\n", vco_freq);
|
|
+
|
|
+ return hdptx_hdmi_phy_config(cdns_phy, p_ctrl_table, p_pll_table, pclk_in);
|
|
+}
|
|
+
|
|
+static int hdptx_hdmi_phy_power_up(struct cdns_hdptx_phy *cdns_phy)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ /* set Power State to A2 */
|
|
+ cdns_phy_reg_write(cdns_phy, PHY_HDP_MODE_CTRL, POWER_STATE_A2);
|
|
+
|
|
+ cdns_phy_reg_write(cdns_phy, TX_DIAG_ACYA_0, 1);
|
|
+ cdns_phy_reg_write(cdns_phy, TX_DIAG_ACYA_1, 1);
|
|
+ cdns_phy_reg_write(cdns_phy, TX_DIAG_ACYA_2, 1);
|
|
+ cdns_phy_reg_write(cdns_phy, TX_DIAG_ACYA_3, 1);
|
|
+
|
|
+ ret = wait_for_ack(cdns_phy, PHY_HDP_MODE_CTRL, POWER_STATE_A2_ACK,
|
|
+ "Wait A2 Ack failed");
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ /* Power up ARC */
|
|
+ hdptx_hdmi_arc_config(cdns_phy);
|
|
+
|
|
+ /* Configure PHY in A0 mode (PHY must be in the A0 power
|
|
+ * state in order to transmit data)
|
|
+ */
|
|
+ cdns_phy_reg_write(cdns_phy, PHY_HDP_MODE_CTRL, POWER_STATE_A0);
|
|
+
|
|
+ return wait_for_ack(cdns_phy, PHY_HDP_MODE_CTRL, POWER_STATE_A0_ACK,
|
|
+ "Wait A0 Ack failed");
|
|
+}
|
|
+
|
|
+static int hdptx_hdmi_phy_power_down(struct cdns_hdptx_phy *cdns_phy)
|
|
+{
|
|
+ u32 val;
|
|
+
|
|
+ val = cdns_phy_reg_read(cdns_phy, PHY_HDP_MODE_CTRL);
|
|
+ val &= ~(POWER_STATE_A0 | POWER_STATE_A1 | POWER_STATE_A2 | POWER_STATE_A3);
|
|
+ /* PHY_DP_MODE_CTL set to A3 power state */
|
|
+ cdns_phy_reg_write(cdns_phy, PHY_HDP_MODE_CTRL, val | POWER_STATE_A3);
|
|
+
|
|
+ return wait_for_ack(cdns_phy, PHY_HDP_MODE_CTRL, POWER_STATE_A3_ACK,
|
|
+ "Wait A3 Ack failed");
|
|
+}
|
|
+
|
|
+static int hdptx_hdmi_configure(struct phy *phy,
|
|
+ union phy_configure_opts *opts)
|
|
+{
|
|
+ struct cdns_hdptx_phy *cdns_phy = phy_get_drvdata(phy);
|
|
+ u32 reg;
|
|
+ int ret;
|
|
+
|
|
+ cdns_phy->hdmi.tmds_char_rate = opts->hdmi.tmds_char_rate;
|
|
+
|
|
+ /* Check HDMI FW alive before HDMI PHY init */
|
|
+ ret = readl_poll_timeout(cdns_phy->regs + KEEP_ALIVE, reg,
|
|
+ reg & CDNS_KEEP_ALIVE_MASK, 500,
|
|
+ CDNS_KEEP_ALIVE_TIMEOUT);
|
|
+ if (ret < 0) {
|
|
+ dev_err(cdns_phy->dev, "NO HDMI FW running\n");
|
|
+ return -ENXIO;
|
|
+ }
|
|
+
|
|
+ /* Configure PHY */
|
|
+ if (hdptx_hdmi_phy_cfg(cdns_phy, cdns_phy->hdmi.tmds_char_rate) < 0) {
|
|
+ dev_err(cdns_phy->dev, "failed to set phy pclock\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ hdptx_hdmi_phy_set_vswing(cdns_phy);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int cdns_hdptx_phy_on(struct phy *phy)
|
|
+{
|
|
+ struct cdns_hdptx_phy *cdns_phy = phy_get_drvdata(phy);
|
|
+
|
|
+ if (phy->attrs.mode == PHY_MODE_DP)
|
|
+ return hdptx_dp_phy_power_up(cdns_phy);
|
|
+ else
|
|
+ return hdptx_hdmi_phy_power_up(cdns_phy);
|
|
+}
|
|
+
|
|
+static int cdns_hdptx_phy_off(struct phy *phy)
|
|
+{
|
|
+ struct cdns_hdptx_phy *cdns_phy = phy_get_drvdata(phy);
|
|
+
|
|
+ if (phy->attrs.mode == PHY_MODE_DP)
|
|
+ return hdptx_dp_phy_power_down(cdns_phy);
|
|
+ else
|
|
+ return hdptx_hdmi_phy_power_down(cdns_phy);
|
|
+}
|
|
+
|
|
+static int
|
|
+cdns_hdptx_phy_valid(struct phy *phy, enum phy_mode mode,
|
|
+ int submode, union phy_configure_opts *opts)
|
|
+{
|
|
+ u32 rate = opts->hdmi.tmds_char_rate / 1000;
|
|
+ int i;
|
|
+
|
|
+ if (mode == PHY_MODE_DP)
|
|
+ return 0;
|
|
+
|
|
+ for (i = 0; i < ARRAY_SIZE(pixel_clk_output_ctrl_table); i++)
|
|
+ if (rate == pixel_clk_output_ctrl_table[i].pixel_clk_freq)
|
|
+ return 0;
|
|
+
|
|
+ return -EINVAL;
|
|
+}
|
|
+
|
|
+static int cdns_hdptx_phy_set_mode(struct phy *phy, enum phy_mode mode, int submode)
|
|
+{
|
|
+ struct cdns_hdptx_phy *cdns_phy = phy_get_drvdata(phy);
|
|
+ int ret = 0;
|
|
+
|
|
+ if (mode == PHY_MODE_DP) {
|
|
+ hdptx_dp_phy_ref_clock_type(cdns_phy);
|
|
+ hdptx_dp_aux_cfg(cdns_phy);
|
|
+ } else if (mode != PHY_MODE_HDMI) {
|
|
+ dev_err(&phy->dev, "Invalid PHY mode: %u\n", mode);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int cdns_hdptx_configure(struct phy *phy,
|
|
+ union phy_configure_opts *opts)
|
|
+{
|
|
+ if (phy->attrs.mode == PHY_MODE_DP)
|
|
+ return hdptx_dp_configure(phy, opts);
|
|
+ else
|
|
+ return hdptx_hdmi_configure(phy, opts);
|
|
+}
|
|
+
|
|
+static const struct phy_ops cdns_hdptx_phy_ops = {
|
|
+ .set_mode = cdns_hdptx_phy_set_mode,
|
|
+ .configure = cdns_hdptx_configure,
|
|
+ .power_on = cdns_hdptx_phy_on,
|
|
+ .power_off = cdns_hdptx_phy_off,
|
|
+ .validate = cdns_hdptx_phy_valid,
|
|
+ .owner = THIS_MODULE,
|
|
+};
|
|
+
|
|
+static int cdns_hdptx_phy_probe(struct platform_device *pdev)
|
|
+{
|
|
+ struct cdns_hdptx_phy *cdns_phy;
|
|
+ struct device *dev = &pdev->dev;
|
|
+ struct device_node *node = dev->of_node;
|
|
+ struct phy_provider *phy_provider;
|
|
+ struct resource *res;
|
|
+ struct phy *phy;
|
|
+ int ret;
|
|
+
|
|
+ cdns_phy = devm_kzalloc(dev, sizeof(*cdns_phy), GFP_KERNEL);
|
|
+ if (!cdns_phy)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ dev_set_drvdata(dev, cdns_phy);
|
|
+ cdns_phy->dev = dev;
|
|
+
|
|
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
+ if (!res)
|
|
+ return -ENODEV;
|
|
+ cdns_phy->regs = devm_ioremap(dev, res->start, resource_size(res));
|
|
+ if (IS_ERR(cdns_phy->regs))
|
|
+ return PTR_ERR(cdns_phy->regs);
|
|
+
|
|
+ phy = devm_phy_create(dev, node, &cdns_hdptx_phy_ops);
|
|
+ if (IS_ERR(phy))
|
|
+ return PTR_ERR(phy);
|
|
+
|
|
+ cdns_phy->phy = phy;
|
|
+ phy_set_drvdata(phy, cdns_phy);
|
|
+
|
|
+ /* init base struct for access mhdp mailbox */
|
|
+ cdns_phy->base.dev = cdns_phy->dev;
|
|
+ cdns_phy->base.regs = cdns_phy->regs;
|
|
+
|
|
+ ret = hdptx_clk_enable(cdns_phy);
|
|
+ if (ret)
|
|
+ return -EINVAL;
|
|
+
|
|
+ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
|
|
+ if (IS_ERR(phy_provider))
|
|
+ return PTR_ERR(phy_provider);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct of_device_id cdns_hdptx_phy_of_match[] = {
|
|
+ {.compatible = "fsl,imx8mq-hdptx-phy" },
|
|
+ { /* sentinel */ }
|
|
+};
|
|
+MODULE_DEVICE_TABLE(of, cdns_hdptx_phy_of_match);
|
|
+
|
|
+static struct platform_driver cdns_hdptx_phy_driver = {
|
|
+ .probe = cdns_hdptx_phy_probe,
|
|
+ .driver = {
|
|
+ .name = "cdns-hdptx-phy",
|
|
+ .of_match_table = cdns_hdptx_phy_of_match,
|
|
+ }
|
|
+};
|
|
+module_platform_driver(cdns_hdptx_phy_driver);
|
|
+
|
|
+MODULE_AUTHOR("Sandor Yu <sandor.yu@nxp.com>");
|
|
+MODULE_DESCRIPTION("Cadence HDP-TX DP/HDMI PHY driver");
|
|
+MODULE_LICENSE("GPL");
|
|
|
|
From patchwork Tue Dec 17 06:51:50 2024
|
|
Content-Type: text/plain; charset="utf-8"
|
|
MIME-Version: 1.0
|
|
Content-Transfer-Encoding: 7bit
|
|
Subject: [v20,8/9] arm64: dts: imx8mq: Add DCSS + HDMI/DP display pipeline
|
|
From: Sandor Yu <Sandor.yu@nxp.com>
|
|
X-Patchwork-Id: 629295
|
|
Message-Id:
|
|
<b730433022fc3af7b271827e86fc4193229862e7.1734340233.git.Sandor.yu@nxp.com>
|
|
To: dmitry.baryshkov@linaro.org, andrzej.hajda@intel.com,
|
|
neil.armstrong@linaro.org, Laurent.pinchart@ideasonboard.com,
|
|
jonas@kwiboo.se, jernej.skrabec@gmail.com, airlied@gmail.com,
|
|
daniel@ffwll.ch, robh+dt@kernel.org, krzysztof.kozlowski+dt@linaro.org,
|
|
shawnguo@kernel.org, s.hauer@pengutronix.de, festevam@gmail.com,
|
|
vkoul@kernel.org, dri-devel@lists.freedesktop.org,
|
|
devicetree@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
|
|
linux-kernel@vger.kernel.org, linux-phy@lists.infradead.org,
|
|
mripard@kernel.org
|
|
Cc: kernel@pengutronix.de, linux-imx@nxp.com, Sandor.yu@nxp.com,
|
|
oliver.brown@nxp.com, alexander.stein@ew.tq-group.com, sam@ravnborg.org
|
|
Date: Tue, 17 Dec 2024 14:51:50 +0800
|
|
|
|
From: Alexander Stein <alexander.stein@ew.tq-group.com>
|
|
|
|
This adds DCSS + MHDP + MHDP PHY nodes. PHY mode (DP/HDMI) is selected
|
|
by the connector type connected to mhdp port@1 endpoint.
|
|
|
|
Signed-off-by: Alexander Stein <alexander.stein@ew.tq-group.com>
|
|
---
|
|
v17->v20:
|
|
*No change
|
|
|
|
arch/arm64/boot/dts/freescale/imx8mq.dtsi | 68 +++++++++++++++++++++++
|
|
1 file changed, 68 insertions(+)
|
|
|
|
diff --git a/arch/arm64/boot/dts/freescale/imx8mq.dtsi b/arch/arm64/boot/dts/freescale/imx8mq.dtsi
|
|
index d51de8d899b2b..df8ba1d5391ae 100644
|
|
--- a/arch/arm64/boot/dts/freescale/imx8mq.dtsi
|
|
+++ b/arch/arm64/boot/dts/freescale/imx8mq.dtsi
|
|
@@ -1602,6 +1602,74 @@ aips4: bus@32c00000 { /* AIPS4 */
|
|
#size-cells = <1>;
|
|
ranges = <0x32c00000 0x32c00000 0x400000>;
|
|
|
|
+ mdhp_phy: phy@32c00000 {
|
|
+ compatible = "fsl,imx8mq-hdptx-phy";
|
|
+ reg = <0x32c00000 0x100000>;
|
|
+ #phy-cells = <0>;
|
|
+ clocks = <&hdmi_phy_27m>, <&clk IMX8MQ_CLK_DISP_APB_ROOT>;
|
|
+ clock-names = "ref", "apb";
|
|
+ };
|
|
+
|
|
+ mhdp: bridge@32c00000 {
|
|
+ compatible = "fsl,imx8mq-mhdp8501";
|
|
+ reg = <0x32c00000 0x100000>;
|
|
+ interrupts = <GIC_SPI 16 IRQ_TYPE_LEVEL_HIGH>,
|
|
+ <GIC_SPI 25 IRQ_TYPE_LEVEL_HIGH>;
|
|
+ interrupt-names = "plug_in", "plug_out";
|
|
+ clocks = <&clk IMX8MQ_CLK_DISP_APB_ROOT>;
|
|
+ phys = <&mdhp_phy>;
|
|
+ status = "disabled";
|
|
+
|
|
+ ports {
|
|
+ #address-cells = <1>;
|
|
+ #size-cells = <0>;
|
|
+
|
|
+ port@0 {
|
|
+ reg = <0>;
|
|
+
|
|
+ mhdp_in: endpoint {
|
|
+ remote-endpoint = <&dcss_out>;
|
|
+ };
|
|
+ };
|
|
+
|
|
+ port@1 {
|
|
+ reg = <1>;
|
|
+
|
|
+ mhdp_out: endpoint {
|
|
+ };
|
|
+ };
|
|
+ };
|
|
+ };
|
|
+
|
|
+ dcss: display-controller@32e00000 {
|
|
+ compatible = "nxp,imx8mq-dcss";
|
|
+ reg = <0x32e00000 0x2d000>, <0x32e2f000 0x1000>;
|
|
+ interrupt-parent = <&irqsteer>;
|
|
+ interrupts = <6>, <8>, <9>;
|
|
+ interrupt-names = "ctxld", "ctxld_kick", "vblank";
|
|
+ clocks = <&clk IMX8MQ_CLK_DISP_APB_ROOT>,
|
|
+ <&clk IMX8MQ_CLK_DISP_AXI_ROOT>,
|
|
+ <&clk IMX8MQ_CLK_DISP_RTRM_ROOT>,
|
|
+ <&clk IMX8MQ_VIDEO2_PLL_OUT>,
|
|
+ <&clk IMX8MQ_CLK_DISP_DTRC>;
|
|
+ clock-names = "apb", "axi", "rtrm", "pix", "dtrc";
|
|
+ assigned-clocks = <&clk IMX8MQ_CLK_DISP_AXI>,
|
|
+ <&clk IMX8MQ_CLK_DISP_RTRM>,
|
|
+ <&clk IMX8MQ_VIDEO2_PLL1_REF_SEL>;
|
|
+ assigned-clock-parents = <&clk IMX8MQ_SYS1_PLL_800M>,
|
|
+ <&clk IMX8MQ_SYS1_PLL_800M>,
|
|
+ <&clk IMX8MQ_CLK_27M>;
|
|
+ assigned-clock-rates = <800000000>,
|
|
+ <400000000>;
|
|
+ status = "disabled";
|
|
+
|
|
+ port {
|
|
+ dcss_out: endpoint {
|
|
+ remote-endpoint = <&mhdp_in>;
|
|
+ };
|
|
+ };
|
|
+ };
|
|
+
|
|
irqsteer: interrupt-controller@32e2d000 {
|
|
compatible = "fsl,imx8m-irqsteer", "fsl,imx-irqsteer";
|
|
reg = <0x32e2d000 0x1000>;
|
|
|
|
From patchwork Tue Dec 17 06:51:51 2024
|
|
Content-Type: text/plain; charset="utf-8"
|
|
MIME-Version: 1.0
|
|
Content-Transfer-Encoding: 7bit
|
|
Subject: [v20,9/9] arm64: dts: imx8mq: tqma8mq-mba8mx: Enable HDMI support
|
|
From: Sandor Yu <Sandor.yu@nxp.com>
|
|
X-Patchwork-Id: 629296
|
|
Message-Id:
|
|
<e72920a199e245d0e1f71f3a62af6c603e4b1f39.1734340233.git.Sandor.yu@nxp.com>
|
|
To: dmitry.baryshkov@linaro.org, andrzej.hajda@intel.com,
|
|
neil.armstrong@linaro.org, Laurent.pinchart@ideasonboard.com,
|
|
jonas@kwiboo.se, jernej.skrabec@gmail.com, airlied@gmail.com,
|
|
daniel@ffwll.ch, robh+dt@kernel.org, krzysztof.kozlowski+dt@linaro.org,
|
|
shawnguo@kernel.org, s.hauer@pengutronix.de, festevam@gmail.com,
|
|
vkoul@kernel.org, dri-devel@lists.freedesktop.org,
|
|
devicetree@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
|
|
linux-kernel@vger.kernel.org, linux-phy@lists.infradead.org,
|
|
mripard@kernel.org
|
|
Cc: kernel@pengutronix.de, linux-imx@nxp.com, Sandor.yu@nxp.com,
|
|
oliver.brown@nxp.com, alexander.stein@ew.tq-group.com, sam@ravnborg.org
|
|
Date: Tue, 17 Dec 2024 14:51:51 +0800
|
|
|
|
From: Alexander Stein <alexander.stein@ew.tq-group.com>
|
|
|
|
Add HDMI connector and connect it to MHDP output. Enable peripherals
|
|
for HDMI output.
|
|
|
|
Signed-off-by: Alexander Stein <alexander.stein@ew.tq-group.com>
|
|
---
|
|
v19->v20:
|
|
*No change
|
|
|
|
v18->v19:
|
|
- Move property data-lanes to endpoint of port@1
|
|
|
|
v17->v18:
|
|
- replace lane-mapping with data-lanes
|
|
|
|
.../dts/freescale/imx8mq-tqma8mq-mba8mx.dts | 26 +++++++++++++++++++
|
|
arch/arm64/boot/dts/freescale/mba8mx.dtsi | 11 ++++++++
|
|
2 files changed, 37 insertions(+)
|
|
|
|
diff --git a/arch/arm64/boot/dts/freescale/imx8mq-tqma8mq-mba8mx.dts b/arch/arm64/boot/dts/freescale/imx8mq-tqma8mq-mba8mx.dts
|
|
index 0165f3a259853..5ba06a411c6a1 100644
|
|
--- a/arch/arm64/boot/dts/freescale/imx8mq-tqma8mq-mba8mx.dts
|
|
+++ b/arch/arm64/boot/dts/freescale/imx8mq-tqma8mq-mba8mx.dts
|
|
@@ -53,6 +53,10 @@ &btn2 {
|
|
gpios = <&gpio3 17 GPIO_ACTIVE_LOW>;
|
|
};
|
|
|
|
+&dcss {
|
|
+ status = "okay";
|
|
+};
|
|
+
|
|
&gpio_leds {
|
|
led3 {
|
|
label = "led3";
|
|
@@ -60,6 +64,14 @@ led3 {
|
|
};
|
|
};
|
|
|
|
+&hdmi_connector {
|
|
+ port {
|
|
+ hdmi_connector_in: endpoint {
|
|
+ remote-endpoint = <&mhdp_out>;
|
|
+ };
|
|
+ };
|
|
+};
|
|
+
|
|
&i2c1 {
|
|
expander2: gpio@25 {
|
|
compatible = "nxp,pca9555";
|
|
@@ -91,6 +103,20 @@ &led2 {
|
|
gpios = <&gpio3 16 GPIO_ACTIVE_HIGH>;
|
|
};
|
|
|
|
+&mhdp {
|
|
+ status = "okay";
|
|
+ ports {
|
|
+ port@1 {
|
|
+ reg = <1>;
|
|
+
|
|
+ mhdp_out: endpoint {
|
|
+ remote-endpoint = <&hdmi_connector_in>;
|
|
+ data-lanes = <0 1 2 3>;
|
|
+ };
|
|
+ };
|
|
+ };
|
|
+};
|
|
+
|
|
/* PCIe slot on X36 */
|
|
&pcie0 {
|
|
reset-gpio = <&expander0 14 GPIO_ACTIVE_LOW>;
|
|
diff --git a/arch/arm64/boot/dts/freescale/mba8mx.dtsi b/arch/arm64/boot/dts/freescale/mba8mx.dtsi
|
|
index 58e3865c28895..d04b75a76dfe6 100644
|
|
--- a/arch/arm64/boot/dts/freescale/mba8mx.dtsi
|
|
+++ b/arch/arm64/boot/dts/freescale/mba8mx.dtsi
|
|
@@ -89,6 +89,17 @@ gpio_delays: gpio-delays {
|
|
gpio-line-names = "LVDS_BRIDGE_EN_1V8";
|
|
};
|
|
|
|
+ hdmi_connector: connector {
|
|
+ compatible = "hdmi-connector";
|
|
+ label = "X11";
|
|
+ type = "a";
|
|
+
|
|
+ port {
|
|
+ hdmi_connector_in: endpoint {
|
|
+ };
|
|
+ };
|
|
+ };
|
|
+
|
|
panel: panel-lvds {
|
|
/*
|
|
* Display is not fixed, so compatible has to be added from
|