162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (C) 2023 Loongson Technology Corporation Limited 462306a36Sopenharmony_ci */ 562306a36Sopenharmony_ci 662306a36Sopenharmony_ci#include <linux/delay.h> 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <drm/drm_atomic_helper.h> 962306a36Sopenharmony_ci#include <drm/drm_debugfs.h> 1062306a36Sopenharmony_ci#include <drm/drm_edid.h> 1162306a36Sopenharmony_ci#include <drm/drm_probe_helper.h> 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_ci#include "lsdc_drv.h" 1462306a36Sopenharmony_ci#include "lsdc_output.h" 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ci/* 1762306a36Sopenharmony_ci * The display controller in LS7A2000 has two display pipes 1862306a36Sopenharmony_ci * Display pipe 0 is attached with a built-in transparent VGA encoder and 1962306a36Sopenharmony_ci * a built-in HDMI encoder. 2062306a36Sopenharmony_ci * Display pipe 1 has only one built-in HDMI encoder connected. 2162306a36Sopenharmony_ci * ______________________ _____________ 2262306a36Sopenharmony_ci * | +-----+ | | | 2362306a36Sopenharmony_ci * | CRTC0 -+--> | VGA | ----> VGA Connector ---> | VGA Monitor |<---+ 2462306a36Sopenharmony_ci * | | +-----+ | |_____________| | 2562306a36Sopenharmony_ci * | | | ______________ | 2662306a36Sopenharmony_ci * | | +------+ | | | | 2762306a36Sopenharmony_ci * | +--> | HDMI | ----> HDMI Connector --> | HDMI Monitor |<--+ 2862306a36Sopenharmony_ci * | +------+ | |______________| | 2962306a36Sopenharmony_ci * | +------+ | | 3062306a36Sopenharmony_ci * | | i2c6 | <-------------------------------------------+ 3162306a36Sopenharmony_ci * | +------+ | 3262306a36Sopenharmony_ci * | | 3362306a36Sopenharmony_ci * | DC in LS7A2000 | 3462306a36Sopenharmony_ci * | | 3562306a36Sopenharmony_ci * | +------+ | 3662306a36Sopenharmony_ci * | | i2c7 | <--------------------------------+ 3762306a36Sopenharmony_ci * | +------+ | | 3862306a36Sopenharmony_ci * | | ______|_______ 3962306a36Sopenharmony_ci * | +------+ | | | 4062306a36Sopenharmony_ci * | CRTC1 ---> | HDMI | ----> HDMI Connector ---> | HDMI Monitor | 4162306a36Sopenharmony_ci * | +------+ | |______________| 4262306a36Sopenharmony_ci * |______________________| 4362306a36Sopenharmony_ci */ 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_cistatic int ls7a2000_connector_get_modes(struct drm_connector *connector) 4662306a36Sopenharmony_ci{ 4762306a36Sopenharmony_ci unsigned int num = 0; 4862306a36Sopenharmony_ci struct edid *edid; 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_ci if (connector->ddc) { 5162306a36Sopenharmony_ci edid = drm_get_edid(connector, connector->ddc); 5262306a36Sopenharmony_ci if (edid) { 5362306a36Sopenharmony_ci drm_connector_update_edid_property(connector, edid); 5462306a36Sopenharmony_ci num = drm_add_edid_modes(connector, edid); 5562306a36Sopenharmony_ci kfree(edid); 5662306a36Sopenharmony_ci } 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci return num; 5962306a36Sopenharmony_ci } 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci num = drm_add_modes_noedid(connector, 1920, 1200); 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci drm_set_preferred_mode(connector, 1024, 768); 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci return num; 6662306a36Sopenharmony_ci} 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_cistatic struct drm_encoder * 6962306a36Sopenharmony_cils7a2000_connector_get_best_encoder(struct drm_connector *connector, 7062306a36Sopenharmony_ci struct drm_atomic_state *state) 7162306a36Sopenharmony_ci{ 7262306a36Sopenharmony_ci struct lsdc_output *output = connector_to_lsdc_output(connector); 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ci return &output->encoder; 7562306a36Sopenharmony_ci} 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_cistatic const struct drm_connector_helper_funcs ls7a2000_connector_helpers = { 7862306a36Sopenharmony_ci .atomic_best_encoder = ls7a2000_connector_get_best_encoder, 7962306a36Sopenharmony_ci .get_modes = ls7a2000_connector_get_modes, 8062306a36Sopenharmony_ci}; 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci/* debugfs */ 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci#define LSDC_HDMI_REG(i, reg) { \ 8562306a36Sopenharmony_ci .name = __stringify_1(LSDC_HDMI##i##_##reg##_REG), \ 8662306a36Sopenharmony_ci .offset = LSDC_HDMI##i##_##reg##_REG, \ 8762306a36Sopenharmony_ci} 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_cistatic const struct lsdc_reg32 ls7a2000_hdmi0_encoder_regs[] = { 9062306a36Sopenharmony_ci LSDC_HDMI_REG(0, ZONE), 9162306a36Sopenharmony_ci LSDC_HDMI_REG(0, INTF_CTRL), 9262306a36Sopenharmony_ci LSDC_HDMI_REG(0, PHY_CTRL), 9362306a36Sopenharmony_ci LSDC_HDMI_REG(0, PHY_PLL), 9462306a36Sopenharmony_ci LSDC_HDMI_REG(0, AVI_INFO_CRTL), 9562306a36Sopenharmony_ci LSDC_HDMI_REG(0, PHY_CAL), 9662306a36Sopenharmony_ci LSDC_HDMI_REG(0, AUDIO_PLL_LO), 9762306a36Sopenharmony_ci LSDC_HDMI_REG(0, AUDIO_PLL_HI), 9862306a36Sopenharmony_ci {NULL, 0}, /* MUST be {NULL, 0} terminated */ 9962306a36Sopenharmony_ci}; 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_cistatic const struct lsdc_reg32 ls7a2000_hdmi1_encoder_regs[] = { 10262306a36Sopenharmony_ci LSDC_HDMI_REG(1, ZONE), 10362306a36Sopenharmony_ci LSDC_HDMI_REG(1, INTF_CTRL), 10462306a36Sopenharmony_ci LSDC_HDMI_REG(1, PHY_CTRL), 10562306a36Sopenharmony_ci LSDC_HDMI_REG(1, PHY_PLL), 10662306a36Sopenharmony_ci LSDC_HDMI_REG(1, AVI_INFO_CRTL), 10762306a36Sopenharmony_ci LSDC_HDMI_REG(1, PHY_CAL), 10862306a36Sopenharmony_ci LSDC_HDMI_REG(1, AUDIO_PLL_LO), 10962306a36Sopenharmony_ci LSDC_HDMI_REG(1, AUDIO_PLL_HI), 11062306a36Sopenharmony_ci {NULL, 0}, /* MUST be {NULL, 0} terminated */ 11162306a36Sopenharmony_ci}; 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_cistatic int ls7a2000_hdmi_encoder_regs_show(struct seq_file *m, void *data) 11462306a36Sopenharmony_ci{ 11562306a36Sopenharmony_ci struct drm_info_node *node = (struct drm_info_node *)m->private; 11662306a36Sopenharmony_ci struct drm_device *ddev = node->minor->dev; 11762306a36Sopenharmony_ci struct lsdc_device *ldev = to_lsdc(ddev); 11862306a36Sopenharmony_ci const struct lsdc_reg32 *preg; 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci preg = (const struct lsdc_reg32 *)node->info_ent->data; 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci while (preg->name) { 12362306a36Sopenharmony_ci u32 offset = preg->offset; 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci seq_printf(m, "%s (0x%04x): 0x%08x\n", 12662306a36Sopenharmony_ci preg->name, offset, lsdc_rreg32(ldev, offset)); 12762306a36Sopenharmony_ci ++preg; 12862306a36Sopenharmony_ci } 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci return 0; 13162306a36Sopenharmony_ci} 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_cistatic const struct drm_info_list ls7a2000_hdmi0_debugfs_files[] = { 13462306a36Sopenharmony_ci { "regs", ls7a2000_hdmi_encoder_regs_show, 0, (void *)ls7a2000_hdmi0_encoder_regs }, 13562306a36Sopenharmony_ci}; 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_cistatic const struct drm_info_list ls7a2000_hdmi1_debugfs_files[] = { 13862306a36Sopenharmony_ci { "regs", ls7a2000_hdmi_encoder_regs_show, 0, (void *)ls7a2000_hdmi1_encoder_regs }, 13962306a36Sopenharmony_ci}; 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_cistatic void ls7a2000_hdmi0_late_register(struct drm_connector *connector, 14262306a36Sopenharmony_ci struct dentry *root) 14362306a36Sopenharmony_ci{ 14462306a36Sopenharmony_ci struct drm_device *ddev = connector->dev; 14562306a36Sopenharmony_ci struct drm_minor *minor = ddev->primary; 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci drm_debugfs_create_files(ls7a2000_hdmi0_debugfs_files, 14862306a36Sopenharmony_ci ARRAY_SIZE(ls7a2000_hdmi0_debugfs_files), 14962306a36Sopenharmony_ci root, minor); 15062306a36Sopenharmony_ci} 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_cistatic void ls7a2000_hdmi1_late_register(struct drm_connector *connector, 15362306a36Sopenharmony_ci struct dentry *root) 15462306a36Sopenharmony_ci{ 15562306a36Sopenharmony_ci struct drm_device *ddev = connector->dev; 15662306a36Sopenharmony_ci struct drm_minor *minor = ddev->primary; 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci drm_debugfs_create_files(ls7a2000_hdmi1_debugfs_files, 15962306a36Sopenharmony_ci ARRAY_SIZE(ls7a2000_hdmi1_debugfs_files), 16062306a36Sopenharmony_ci root, minor); 16162306a36Sopenharmony_ci} 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci/* monitor present detection */ 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_cistatic enum drm_connector_status 16662306a36Sopenharmony_cils7a2000_hdmi0_vga_connector_detect(struct drm_connector *connector, bool force) 16762306a36Sopenharmony_ci{ 16862306a36Sopenharmony_ci struct drm_device *ddev = connector->dev; 16962306a36Sopenharmony_ci struct lsdc_device *ldev = to_lsdc(ddev); 17062306a36Sopenharmony_ci u32 val; 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci val = lsdc_rreg32(ldev, LSDC_HDMI_HPD_STATUS_REG); 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci if (val & HDMI0_HPD_FLAG) 17562306a36Sopenharmony_ci return connector_status_connected; 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci if (connector->ddc) { 17862306a36Sopenharmony_ci if (drm_probe_ddc(connector->ddc)) 17962306a36Sopenharmony_ci return connector_status_connected; 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci return connector_status_disconnected; 18262306a36Sopenharmony_ci } 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci return connector_status_unknown; 18562306a36Sopenharmony_ci} 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_cistatic enum drm_connector_status 18862306a36Sopenharmony_cils7a2000_hdmi1_connector_detect(struct drm_connector *connector, bool force) 18962306a36Sopenharmony_ci{ 19062306a36Sopenharmony_ci struct lsdc_device *ldev = to_lsdc(connector->dev); 19162306a36Sopenharmony_ci u32 val; 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ci val = lsdc_rreg32(ldev, LSDC_HDMI_HPD_STATUS_REG); 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci if (val & HDMI1_HPD_FLAG) 19662306a36Sopenharmony_ci return connector_status_connected; 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci return connector_status_disconnected; 19962306a36Sopenharmony_ci} 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_cistatic const struct drm_connector_funcs ls7a2000_hdmi_connector_funcs[2] = { 20262306a36Sopenharmony_ci { 20362306a36Sopenharmony_ci .detect = ls7a2000_hdmi0_vga_connector_detect, 20462306a36Sopenharmony_ci .fill_modes = drm_helper_probe_single_connector_modes, 20562306a36Sopenharmony_ci .destroy = drm_connector_cleanup, 20662306a36Sopenharmony_ci .reset = drm_atomic_helper_connector_reset, 20762306a36Sopenharmony_ci .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, 20862306a36Sopenharmony_ci .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, 20962306a36Sopenharmony_ci .debugfs_init = ls7a2000_hdmi0_late_register, 21062306a36Sopenharmony_ci }, 21162306a36Sopenharmony_ci { 21262306a36Sopenharmony_ci .detect = ls7a2000_hdmi1_connector_detect, 21362306a36Sopenharmony_ci .fill_modes = drm_helper_probe_single_connector_modes, 21462306a36Sopenharmony_ci .destroy = drm_connector_cleanup, 21562306a36Sopenharmony_ci .reset = drm_atomic_helper_connector_reset, 21662306a36Sopenharmony_ci .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, 21762306a36Sopenharmony_ci .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, 21862306a36Sopenharmony_ci .debugfs_init = ls7a2000_hdmi1_late_register, 21962306a36Sopenharmony_ci }, 22062306a36Sopenharmony_ci}; 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci/* Even though some board has only one hdmi on display pipe 1, 22362306a36Sopenharmony_ci * We still need hook lsdc_encoder_funcs up on display pipe 0, 22462306a36Sopenharmony_ci * This is because we need its reset() callback get called, to 22562306a36Sopenharmony_ci * set the LSDC_HDMIx_CTRL_REG using software gpio emulated i2c. 22662306a36Sopenharmony_ci * Otherwise, the firmware may set LSDC_HDMIx_CTRL_REG blindly. 22762306a36Sopenharmony_ci */ 22862306a36Sopenharmony_cistatic void ls7a2000_hdmi0_encoder_reset(struct drm_encoder *encoder) 22962306a36Sopenharmony_ci{ 23062306a36Sopenharmony_ci struct drm_device *ddev = encoder->dev; 23162306a36Sopenharmony_ci struct lsdc_device *ldev = to_lsdc(ddev); 23262306a36Sopenharmony_ci u32 val; 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_ci val = PHY_CLOCK_POL | PHY_CLOCK_EN | PHY_DATA_EN; 23562306a36Sopenharmony_ci lsdc_wreg32(ldev, LSDC_CRTC0_DVO_CONF_REG, val); 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_ci /* using software gpio emulated i2c */ 23862306a36Sopenharmony_ci val = lsdc_rreg32(ldev, LSDC_HDMI0_INTF_CTRL_REG); 23962306a36Sopenharmony_ci val &= ~HW_I2C_EN; 24062306a36Sopenharmony_ci lsdc_wreg32(ldev, LSDC_HDMI0_INTF_CTRL_REG, val); 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_ci /* help the hdmi phy to get out of reset state */ 24362306a36Sopenharmony_ci lsdc_wreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, HDMI_PHY_RESET_N); 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_ci mdelay(20); 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_ci drm_dbg(ddev, "HDMI-0 Reset\n"); 24862306a36Sopenharmony_ci} 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_cistatic void ls7a2000_hdmi1_encoder_reset(struct drm_encoder *encoder) 25162306a36Sopenharmony_ci{ 25262306a36Sopenharmony_ci struct drm_device *ddev = encoder->dev; 25362306a36Sopenharmony_ci struct lsdc_device *ldev = to_lsdc(ddev); 25462306a36Sopenharmony_ci u32 val; 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci val = PHY_CLOCK_POL | PHY_CLOCK_EN | PHY_DATA_EN; 25762306a36Sopenharmony_ci lsdc_wreg32(ldev, LSDC_CRTC1_DVO_CONF_REG, val); 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ci /* using software gpio emulated i2c */ 26062306a36Sopenharmony_ci val = lsdc_rreg32(ldev, LSDC_HDMI1_INTF_CTRL_REG); 26162306a36Sopenharmony_ci val &= ~HW_I2C_EN; 26262306a36Sopenharmony_ci lsdc_wreg32(ldev, LSDC_HDMI1_INTF_CTRL_REG, val); 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_ci /* help the hdmi phy to get out of reset state */ 26562306a36Sopenharmony_ci lsdc_wreg32(ldev, LSDC_HDMI1_PHY_CTRL_REG, HDMI_PHY_RESET_N); 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_ci mdelay(20); 26862306a36Sopenharmony_ci 26962306a36Sopenharmony_ci drm_dbg(ddev, "HDMI-1 Reset\n"); 27062306a36Sopenharmony_ci} 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_cistatic const struct drm_encoder_funcs ls7a2000_encoder_funcs[2] = { 27362306a36Sopenharmony_ci { 27462306a36Sopenharmony_ci .reset = ls7a2000_hdmi0_encoder_reset, 27562306a36Sopenharmony_ci .destroy = drm_encoder_cleanup, 27662306a36Sopenharmony_ci }, 27762306a36Sopenharmony_ci { 27862306a36Sopenharmony_ci .reset = ls7a2000_hdmi1_encoder_reset, 27962306a36Sopenharmony_ci .destroy = drm_encoder_cleanup, 28062306a36Sopenharmony_ci }, 28162306a36Sopenharmony_ci}; 28262306a36Sopenharmony_ci 28362306a36Sopenharmony_cistatic int ls7a2000_hdmi_set_avi_infoframe(struct drm_encoder *encoder, 28462306a36Sopenharmony_ci struct drm_display_mode *mode) 28562306a36Sopenharmony_ci{ 28662306a36Sopenharmony_ci struct lsdc_output *output = encoder_to_lsdc_output(encoder); 28762306a36Sopenharmony_ci struct lsdc_display_pipe *dispipe = output_to_display_pipe(output); 28862306a36Sopenharmony_ci unsigned int index = dispipe->index; 28962306a36Sopenharmony_ci struct drm_device *ddev = encoder->dev; 29062306a36Sopenharmony_ci struct lsdc_device *ldev = to_lsdc(ddev); 29162306a36Sopenharmony_ci struct hdmi_avi_infoframe infoframe; 29262306a36Sopenharmony_ci u8 buffer[HDMI_INFOFRAME_SIZE(AVI)]; 29362306a36Sopenharmony_ci unsigned char *ptr = &buffer[HDMI_INFOFRAME_HEADER_SIZE]; 29462306a36Sopenharmony_ci unsigned int content0, content1, content2, content3; 29562306a36Sopenharmony_ci int err; 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_ci err = drm_hdmi_avi_infoframe_from_display_mode(&infoframe, 29862306a36Sopenharmony_ci &output->connector, 29962306a36Sopenharmony_ci mode); 30062306a36Sopenharmony_ci if (err < 0) { 30162306a36Sopenharmony_ci drm_err(ddev, "failed to setup AVI infoframe: %d\n", err); 30262306a36Sopenharmony_ci return err; 30362306a36Sopenharmony_ci } 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_ci /* Fixed infoframe configuration not linked to the mode */ 30662306a36Sopenharmony_ci infoframe.colorspace = HDMI_COLORSPACE_RGB; 30762306a36Sopenharmony_ci infoframe.quantization_range = HDMI_QUANTIZATION_RANGE_DEFAULT; 30862306a36Sopenharmony_ci infoframe.colorimetry = HDMI_COLORIMETRY_NONE; 30962306a36Sopenharmony_ci 31062306a36Sopenharmony_ci err = hdmi_avi_infoframe_pack(&infoframe, buffer, sizeof(buffer)); 31162306a36Sopenharmony_ci if (err < 0) { 31262306a36Sopenharmony_ci drm_err(ddev, "failed to pack AVI infoframe: %d\n", err); 31362306a36Sopenharmony_ci return err; 31462306a36Sopenharmony_ci } 31562306a36Sopenharmony_ci 31662306a36Sopenharmony_ci content0 = *(unsigned int *)ptr; 31762306a36Sopenharmony_ci content1 = *(ptr + 4); 31862306a36Sopenharmony_ci content2 = *(unsigned int *)(ptr + 5); 31962306a36Sopenharmony_ci content3 = *(unsigned int *)(ptr + 9); 32062306a36Sopenharmony_ci 32162306a36Sopenharmony_ci lsdc_pipe_wreg32(ldev, LSDC_HDMI0_AVI_CONTENT0, index, content0); 32262306a36Sopenharmony_ci lsdc_pipe_wreg32(ldev, LSDC_HDMI0_AVI_CONTENT1, index, content1); 32362306a36Sopenharmony_ci lsdc_pipe_wreg32(ldev, LSDC_HDMI0_AVI_CONTENT2, index, content2); 32462306a36Sopenharmony_ci lsdc_pipe_wreg32(ldev, LSDC_HDMI0_AVI_CONTENT3, index, content3); 32562306a36Sopenharmony_ci 32662306a36Sopenharmony_ci lsdc_pipe_wreg32(ldev, LSDC_HDMI0_AVI_INFO_CRTL_REG, index, 32762306a36Sopenharmony_ci AVI_PKT_ENABLE | AVI_PKT_UPDATE); 32862306a36Sopenharmony_ci 32962306a36Sopenharmony_ci drm_dbg(ddev, "Update HDMI-%u avi infoframe\n", index); 33062306a36Sopenharmony_ci 33162306a36Sopenharmony_ci return 0; 33262306a36Sopenharmony_ci} 33362306a36Sopenharmony_ci 33462306a36Sopenharmony_cistatic void ls7a2000_hdmi_atomic_disable(struct drm_encoder *encoder, 33562306a36Sopenharmony_ci struct drm_atomic_state *state) 33662306a36Sopenharmony_ci{ 33762306a36Sopenharmony_ci struct lsdc_output *output = encoder_to_lsdc_output(encoder); 33862306a36Sopenharmony_ci struct lsdc_display_pipe *dispipe = output_to_display_pipe(output); 33962306a36Sopenharmony_ci unsigned int index = dispipe->index; 34062306a36Sopenharmony_ci struct drm_device *ddev = encoder->dev; 34162306a36Sopenharmony_ci struct lsdc_device *ldev = to_lsdc(ddev); 34262306a36Sopenharmony_ci u32 val; 34362306a36Sopenharmony_ci 34462306a36Sopenharmony_ci /* Disable the hdmi phy */ 34562306a36Sopenharmony_ci val = lsdc_pipe_rreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, index); 34662306a36Sopenharmony_ci val &= ~HDMI_PHY_EN; 34762306a36Sopenharmony_ci lsdc_pipe_wreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, index, val); 34862306a36Sopenharmony_ci 34962306a36Sopenharmony_ci /* Disable the hdmi interface */ 35062306a36Sopenharmony_ci val = lsdc_pipe_rreg32(ldev, LSDC_HDMI0_INTF_CTRL_REG, index); 35162306a36Sopenharmony_ci val &= ~HDMI_INTERFACE_EN; 35262306a36Sopenharmony_ci lsdc_pipe_wreg32(ldev, LSDC_HDMI0_INTF_CTRL_REG, index, val); 35362306a36Sopenharmony_ci 35462306a36Sopenharmony_ci drm_dbg(ddev, "HDMI-%u disabled\n", index); 35562306a36Sopenharmony_ci} 35662306a36Sopenharmony_ci 35762306a36Sopenharmony_cistatic void ls7a2000_hdmi_atomic_enable(struct drm_encoder *encoder, 35862306a36Sopenharmony_ci struct drm_atomic_state *state) 35962306a36Sopenharmony_ci{ 36062306a36Sopenharmony_ci struct drm_device *ddev = encoder->dev; 36162306a36Sopenharmony_ci struct lsdc_device *ldev = to_lsdc(ddev); 36262306a36Sopenharmony_ci struct lsdc_output *output = encoder_to_lsdc_output(encoder); 36362306a36Sopenharmony_ci struct lsdc_display_pipe *dispipe = output_to_display_pipe(output); 36462306a36Sopenharmony_ci unsigned int index = dispipe->index; 36562306a36Sopenharmony_ci u32 val; 36662306a36Sopenharmony_ci 36762306a36Sopenharmony_ci /* datasheet say it should larger than 48 */ 36862306a36Sopenharmony_ci val = 64 << HDMI_H_ZONE_IDLE_SHIFT | 64 << HDMI_V_ZONE_IDLE_SHIFT; 36962306a36Sopenharmony_ci 37062306a36Sopenharmony_ci lsdc_pipe_wreg32(ldev, LSDC_HDMI0_ZONE_REG, index, val); 37162306a36Sopenharmony_ci 37262306a36Sopenharmony_ci val = HDMI_PHY_TERM_STATUS | 37362306a36Sopenharmony_ci HDMI_PHY_TERM_DET_EN | 37462306a36Sopenharmony_ci HDMI_PHY_TERM_H_EN | 37562306a36Sopenharmony_ci HDMI_PHY_TERM_L_EN | 37662306a36Sopenharmony_ci HDMI_PHY_RESET_N | 37762306a36Sopenharmony_ci HDMI_PHY_EN; 37862306a36Sopenharmony_ci 37962306a36Sopenharmony_ci lsdc_pipe_wreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, index, val); 38062306a36Sopenharmony_ci 38162306a36Sopenharmony_ci udelay(2); 38262306a36Sopenharmony_ci 38362306a36Sopenharmony_ci val = HDMI_CTL_PERIOD_MODE | 38462306a36Sopenharmony_ci HDMI_AUDIO_EN | 38562306a36Sopenharmony_ci HDMI_PACKET_EN | 38662306a36Sopenharmony_ci HDMI_INTERFACE_EN | 38762306a36Sopenharmony_ci (8 << HDMI_VIDEO_PREAMBLE_SHIFT); 38862306a36Sopenharmony_ci 38962306a36Sopenharmony_ci lsdc_pipe_wreg32(ldev, LSDC_HDMI0_INTF_CTRL_REG, index, val); 39062306a36Sopenharmony_ci 39162306a36Sopenharmony_ci drm_dbg(ddev, "HDMI-%u enabled\n", index); 39262306a36Sopenharmony_ci} 39362306a36Sopenharmony_ci 39462306a36Sopenharmony_ci/* 39562306a36Sopenharmony_ci * Fout = M * Fin 39662306a36Sopenharmony_ci * 39762306a36Sopenharmony_ci * M = (4 * LF) / (IDF * ODF) 39862306a36Sopenharmony_ci * 39962306a36Sopenharmony_ci * IDF: Input Division Factor 40062306a36Sopenharmony_ci * ODF: Output Division Factor 40162306a36Sopenharmony_ci * LF: Loop Factor 40262306a36Sopenharmony_ci * M: Required Mult 40362306a36Sopenharmony_ci * 40462306a36Sopenharmony_ci * +--------------------------------------------------------+ 40562306a36Sopenharmony_ci * | Fin (kHZ) | M | IDF | LF | ODF | Fout(Mhz) | 40662306a36Sopenharmony_ci * |-------------------+----+-----+----+-----+--------------| 40762306a36Sopenharmony_ci * | 170000 ~ 340000 | 10 | 16 | 40 | 1 | 1700 ~ 3400 | 40862306a36Sopenharmony_ci * | 85000 ~ 170000 | 10 | 8 | 40 | 2 | 850 ~ 1700 | 40962306a36Sopenharmony_ci * | 42500 ~ 85000 | 10 | 4 | 40 | 4 | 425 ~ 850 | 41062306a36Sopenharmony_ci * | 21250 ~ 42500 | 10 | 2 | 40 | 8 | 212.5 ~ 425 | 41162306a36Sopenharmony_ci * | 20000 ~ 21250 | 10 | 1 | 40 | 16 | 200 ~ 212.5 | 41262306a36Sopenharmony_ci * +--------------------------------------------------------+ 41362306a36Sopenharmony_ci */ 41462306a36Sopenharmony_cistatic void ls7a2000_hdmi_phy_pll_config(struct lsdc_device *ldev, 41562306a36Sopenharmony_ci int fin, 41662306a36Sopenharmony_ci unsigned int index) 41762306a36Sopenharmony_ci{ 41862306a36Sopenharmony_ci struct drm_device *ddev = &ldev->base; 41962306a36Sopenharmony_ci int count = 0; 42062306a36Sopenharmony_ci u32 val; 42162306a36Sopenharmony_ci 42262306a36Sopenharmony_ci /* Firstly, disable phy pll */ 42362306a36Sopenharmony_ci lsdc_pipe_wreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, index, 0x0); 42462306a36Sopenharmony_ci 42562306a36Sopenharmony_ci /* 42662306a36Sopenharmony_ci * Most of time, loongson HDMI require M = 10 42762306a36Sopenharmony_ci * for example, 10 = (4 * 40) / (8 * 2) 42862306a36Sopenharmony_ci * here, write "1" to the ODF will get "2" 42962306a36Sopenharmony_ci */ 43062306a36Sopenharmony_ci 43162306a36Sopenharmony_ci if (fin >= 170000) 43262306a36Sopenharmony_ci val = (16 << HDMI_PLL_IDF_SHIFT) | 43362306a36Sopenharmony_ci (40 << HDMI_PLL_LF_SHIFT) | 43462306a36Sopenharmony_ci (0 << HDMI_PLL_ODF_SHIFT); 43562306a36Sopenharmony_ci else if (fin >= 85000) 43662306a36Sopenharmony_ci val = (8 << HDMI_PLL_IDF_SHIFT) | 43762306a36Sopenharmony_ci (40 << HDMI_PLL_LF_SHIFT) | 43862306a36Sopenharmony_ci (1 << HDMI_PLL_ODF_SHIFT); 43962306a36Sopenharmony_ci else if (fin >= 42500) 44062306a36Sopenharmony_ci val = (4 << HDMI_PLL_IDF_SHIFT) | 44162306a36Sopenharmony_ci (40 << HDMI_PLL_LF_SHIFT) | 44262306a36Sopenharmony_ci (2 << HDMI_PLL_ODF_SHIFT); 44362306a36Sopenharmony_ci else if (fin >= 21250) 44462306a36Sopenharmony_ci val = (2 << HDMI_PLL_IDF_SHIFT) | 44562306a36Sopenharmony_ci (40 << HDMI_PLL_LF_SHIFT) | 44662306a36Sopenharmony_ci (3 << HDMI_PLL_ODF_SHIFT); 44762306a36Sopenharmony_ci else 44862306a36Sopenharmony_ci val = (1 << HDMI_PLL_IDF_SHIFT) | 44962306a36Sopenharmony_ci (40 << HDMI_PLL_LF_SHIFT) | 45062306a36Sopenharmony_ci (4 << HDMI_PLL_ODF_SHIFT); 45162306a36Sopenharmony_ci 45262306a36Sopenharmony_ci lsdc_pipe_wreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, index, val); 45362306a36Sopenharmony_ci 45462306a36Sopenharmony_ci val |= HDMI_PLL_ENABLE; 45562306a36Sopenharmony_ci 45662306a36Sopenharmony_ci lsdc_pipe_wreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, index, val); 45762306a36Sopenharmony_ci 45862306a36Sopenharmony_ci udelay(2); 45962306a36Sopenharmony_ci 46062306a36Sopenharmony_ci drm_dbg(ddev, "Fin of HDMI-%u: %d kHz\n", index, fin); 46162306a36Sopenharmony_ci 46262306a36Sopenharmony_ci /* Wait hdmi phy pll lock */ 46362306a36Sopenharmony_ci do { 46462306a36Sopenharmony_ci val = lsdc_pipe_rreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, index); 46562306a36Sopenharmony_ci 46662306a36Sopenharmony_ci if (val & HDMI_PLL_LOCKED) { 46762306a36Sopenharmony_ci drm_dbg(ddev, "Setting HDMI-%u PLL take %d cycles\n", 46862306a36Sopenharmony_ci index, count); 46962306a36Sopenharmony_ci break; 47062306a36Sopenharmony_ci } 47162306a36Sopenharmony_ci ++count; 47262306a36Sopenharmony_ci } while (count < 1000); 47362306a36Sopenharmony_ci 47462306a36Sopenharmony_ci lsdc_pipe_wreg32(ldev, LSDC_HDMI0_PHY_CAL_REG, index, 0x0f000ff0); 47562306a36Sopenharmony_ci 47662306a36Sopenharmony_ci if (count >= 1000) 47762306a36Sopenharmony_ci drm_err(ddev, "Setting HDMI-%u PLL failed\n", index); 47862306a36Sopenharmony_ci} 47962306a36Sopenharmony_ci 48062306a36Sopenharmony_cistatic void ls7a2000_hdmi_atomic_mode_set(struct drm_encoder *encoder, 48162306a36Sopenharmony_ci struct drm_crtc_state *crtc_state, 48262306a36Sopenharmony_ci struct drm_connector_state *conn_state) 48362306a36Sopenharmony_ci{ 48462306a36Sopenharmony_ci struct lsdc_output *output = encoder_to_lsdc_output(encoder); 48562306a36Sopenharmony_ci struct lsdc_display_pipe *dispipe = output_to_display_pipe(output); 48662306a36Sopenharmony_ci unsigned int index = dispipe->index; 48762306a36Sopenharmony_ci struct drm_device *ddev = encoder->dev; 48862306a36Sopenharmony_ci struct lsdc_device *ldev = to_lsdc(ddev); 48962306a36Sopenharmony_ci struct drm_display_mode *mode = &crtc_state->mode; 49062306a36Sopenharmony_ci 49162306a36Sopenharmony_ci ls7a2000_hdmi_phy_pll_config(ldev, mode->clock, index); 49262306a36Sopenharmony_ci 49362306a36Sopenharmony_ci ls7a2000_hdmi_set_avi_infoframe(encoder, mode); 49462306a36Sopenharmony_ci 49562306a36Sopenharmony_ci drm_dbg(ddev, "%s modeset finished\n", encoder->name); 49662306a36Sopenharmony_ci} 49762306a36Sopenharmony_ci 49862306a36Sopenharmony_cistatic const struct drm_encoder_helper_funcs ls7a2000_encoder_helper_funcs = { 49962306a36Sopenharmony_ci .atomic_disable = ls7a2000_hdmi_atomic_disable, 50062306a36Sopenharmony_ci .atomic_enable = ls7a2000_hdmi_atomic_enable, 50162306a36Sopenharmony_ci .atomic_mode_set = ls7a2000_hdmi_atomic_mode_set, 50262306a36Sopenharmony_ci}; 50362306a36Sopenharmony_ci 50462306a36Sopenharmony_ci/* 50562306a36Sopenharmony_ci * For LS7A2000: 50662306a36Sopenharmony_ci * 50762306a36Sopenharmony_ci * 1) Most of board export one vga + hdmi output interface. 50862306a36Sopenharmony_ci * 2) Yet, Some boards export double hdmi output interface. 50962306a36Sopenharmony_ci * 3) Still have boards export three output(2 hdmi + 1 vga). 51062306a36Sopenharmony_ci * 51162306a36Sopenharmony_ci * So let's hook hdmi helper funcs to all display pipe, don't miss. 51262306a36Sopenharmony_ci * writing hdmi register do no harms. 51362306a36Sopenharmony_ci */ 51462306a36Sopenharmony_ciint ls7a2000_output_init(struct drm_device *ddev, 51562306a36Sopenharmony_ci struct lsdc_display_pipe *dispipe, 51662306a36Sopenharmony_ci struct i2c_adapter *ddc, 51762306a36Sopenharmony_ci unsigned int pipe) 51862306a36Sopenharmony_ci{ 51962306a36Sopenharmony_ci struct lsdc_output *output = &dispipe->output; 52062306a36Sopenharmony_ci struct drm_encoder *encoder = &output->encoder; 52162306a36Sopenharmony_ci struct drm_connector *connector = &output->connector; 52262306a36Sopenharmony_ci int ret; 52362306a36Sopenharmony_ci 52462306a36Sopenharmony_ci ret = drm_encoder_init(ddev, encoder, &ls7a2000_encoder_funcs[pipe], 52562306a36Sopenharmony_ci DRM_MODE_ENCODER_TMDS, "encoder-%u", pipe); 52662306a36Sopenharmony_ci if (ret) 52762306a36Sopenharmony_ci return ret; 52862306a36Sopenharmony_ci 52962306a36Sopenharmony_ci encoder->possible_crtcs = BIT(pipe); 53062306a36Sopenharmony_ci 53162306a36Sopenharmony_ci drm_encoder_helper_add(encoder, &ls7a2000_encoder_helper_funcs); 53262306a36Sopenharmony_ci 53362306a36Sopenharmony_ci ret = drm_connector_init_with_ddc(ddev, connector, 53462306a36Sopenharmony_ci &ls7a2000_hdmi_connector_funcs[pipe], 53562306a36Sopenharmony_ci DRM_MODE_CONNECTOR_HDMIA, ddc); 53662306a36Sopenharmony_ci if (ret) 53762306a36Sopenharmony_ci return ret; 53862306a36Sopenharmony_ci 53962306a36Sopenharmony_ci drm_info(ddev, "display pipe-%u has HDMI %s\n", pipe, pipe ? "" : "and/or VGA"); 54062306a36Sopenharmony_ci 54162306a36Sopenharmony_ci drm_connector_helper_add(connector, &ls7a2000_connector_helpers); 54262306a36Sopenharmony_ci 54362306a36Sopenharmony_ci drm_connector_attach_encoder(connector, encoder); 54462306a36Sopenharmony_ci 54562306a36Sopenharmony_ci connector->polled = DRM_CONNECTOR_POLL_CONNECT | 54662306a36Sopenharmony_ci DRM_CONNECTOR_POLL_DISCONNECT; 54762306a36Sopenharmony_ci 54862306a36Sopenharmony_ci connector->interlace_allowed = 0; 54962306a36Sopenharmony_ci connector->doublescan_allowed = 0; 55062306a36Sopenharmony_ci 55162306a36Sopenharmony_ci return 0; 55262306a36Sopenharmony_ci} 553