From bf1acefa6ef424caa4c0b37ae022d25c1028984f Mon Sep 17 00:00:00 2001 From: Liu Ying <victor.liu@nxp.com> Date: Mon, 27 Mar 2017 17:26:59 +0800 Subject: [PATCH] MLK-15001-25 drm/bridge: Add ITE IT6263 LVDS to HDMI transmitter support This patch adds IT6263 video support. Signed-off-by: Liu Ying <victor.liu@nxp.com> --- .../bindings/display/bridge/it6263.txt | 28 + drivers/gpu/drm/bridge/Kconfig | 8 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/it6263.c | 859 ++++++++++++++++++ 4 files changed, 896 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/bridge/it6263.txt create mode 100644 drivers/gpu/drm/bridge/it6263.c diff --git a/Documentation/devicetree/bindings/display/bridge/it6263.txt b/Documentation/devicetree/bindings/display/bridge/it6263.txt new file mode 100644 index 00000000000000..c33ee6942b1998 --- /dev/null +++ b/Documentation/devicetree/bindings/display/bridge/it6263.txt @@ -0,0 +1,28 @@ +ITE IT6263 LVDS to HDMI bridge bindings + +Required properties: + - compatible: "ite,it6263" + - reg: i2c address of the bridge + - video input: this subnode can contain a video input port node + to connect the bridge to a LVDS output interface (See this + documentation [1]). + +Optional properties: + - split-mode: boolean. if this exists, split mode is enabled, + otherwise, single mode is enabled. + +[1]: Documentation/devicetree/bindings/media/video-interfaces.txt + +Example: + lvds-to-hdmi-bridge@4c { + compatible = "ite,it6263"; + reg = <0x4c>; + + port { + it6263_0_in: endpoint { + clock-lanes = <3>; + data-lanes = <0 1 2 4 5>; + remote-endpoint = <&lvds0_out>; + }; + }; + }; diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index adf9ae0e0b7c9d..dc42b04a57671c 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -106,4 +106,12 @@ source "drivers/gpu/drm/bridge/adv7511/Kconfig" source "drivers/gpu/drm/bridge/synopsys/Kconfig" +config DRM_ITE_IT6263 + tristate "ITE IT6263 LVDS/HDMI bridge" + depends on OF + select DRM_KMS_HELPER + select REGMAP_I2C + ---help--- + ITE IT6263 bridge chip driver. + endmenu diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile index 60dab87e4783cf..b3998a9a621b0b 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -12,3 +12,4 @@ obj-$(CONFIG_DRM_ANALOGIX_DP) += analogix/ obj-$(CONFIG_DRM_I2C_ADV7511) += adv7511/ obj-$(CONFIG_DRM_TI_TFP410) += ti-tfp410.o obj-y += synopsys/ +obj-$(CONFIG_DRM_ITE_IT6263) += it6263.o diff --git a/drivers/gpu/drm/bridge/it6263.c b/drivers/gpu/drm/bridge/it6263.c new file mode 100644 index 00000000000000..e7aef7745a5564 --- /dev/null +++ b/drivers/gpu/drm/bridge/it6263.c @@ -0,0 +1,859 @@ +/* + * Copyright 2017 NXP + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include <drm/drmP.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_edid.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/regmap.h> + +#define REG_VENDOR_ID(n) (0x00 + (n)) /* n: 0/1 */ +#define REG_DEVICE_ID(n) (0x02 + (n)) /* n: 0/1 */ +#define LVDS_VENDER_ID_LOW 0x15 +#define LVDS_VENDER_ID_HIGH 0xCA +#define LVDS_DEVICE_ID_LOW 0x61 +#define LVDS_DEVICE_ID_HIGH 0x62 +#define HDMI_VENDER_ID_LOW 0x01 +#define HDMI_VENDER_ID_HIGH 0xCA +#define HDMI_DEVICE_ID_LOW 0x13 +#define HDMI_DEVICE_ID_HIGH 0x76 + +/* LVDS registers */ +#define LVDS_REG_SW_RST 0x05 +#define SOFT_REFCLK_DM_RST BIT(0) +#define SOFT_PCLK_DM_RST BIT(1) + +#define LVDS_REG_MODE 0x2C +#define LVDS_COLOR_DEPTH 0x3 +enum { + LVDS_COLOR_DEPTH_18, + LVDS_COLOR_DEPTH_24, + LVDS_COLOR_DEPTH_30, + LVDS_COLOR_DEPTH_36, +}; +#define LVDS_OUT_MAP BIT(4) +#define VESA BIT(4) +#define JEIDA 0 +#define DMODE BIT(7) +#define SPLIT_MODE BIT(7) +#define SINGLE_MODE 0 + +#define LVDS_REG_STABLE 0x30 +#define VIDEO_STABLE BIT(0) +#define PCLK_LOCK BIT(1) + +#define LVDS_REG_39 0x39 + +#define LVDS_REG_PLL 0x3C +#define LVDS_REG_AFE_3E 0x3E +#define LVDS_REG_AFE_3F 0x3F +#define LVDS_REG_AFE_47 0x47 +#define LVDS_REG_AFE_48 0x48 +#define LVDS_REG_AFE_4F 0x4F +#define LVDS_REG_52 0x52 +#define LVDS_REG_PCLK_CNT_HIGH 0x57 +#define LVDS_REG_PCLK_CNT_LOW 0x58 + +/* + * HDMI registers + * + * Registers are separated into three banks: + * 1) common bank: 0x00 ~ 0x2F + * 2) bank0: 0x30 ~ 0xFF + * 3) bank1: 0x130 ~ 0x1FF (HDMI packet registers) + * + * Use register HDMI_REG_BANK_CTRL @ 0x0F[1:0] to select bank0/1: + * 2b'00 - bank0 + * 2b'01 - bank1 + */ + +/******************************/ +/* HDMI register common bank */ +/******************************/ + +/* HDMI genernal registers */ +#define HDMI_REG_SW_RST 0x04 +#define SOFTREF_RST BIT(5) +#define SOFTA_RST BIT(4) +#define SOFTV_RST BIT(3) +#define AUD_RST BIT(2) +#define HDCP_RST BIT(0) +#define HDMI_RST_ALL (SOFTREF_RST | SOFTA_RST | SOFTV_RST | \ + AUD_RST | HDCP_RST) + +#define HDMI_REG_INT_CTRL 0x05 +#define INTPOL_ACTH BIT(7) +#define INTPOL_ACTL 0 +#define INTIOMODE_OPENDRAIN BIT(6) +#define INTIOMODE_PUSHPULL 0 +#define SELXTAL BIT(5) /* REFCLK <= XTALCLK */ +#define SELXTAL_QUARTER 0 /* REFCLK <= OSCCLK/4 */ +#define PDREFCNT(n) (((n) >> 2) << 2) /* REFCLK Div(n) */ +#define PDREFCLK BIT(1) +#define PDTXCLK_GATED BIT(0) +#define PDTXCLK_ACTIVE 0 + +#define HDMI_REG_INT_STAT(n) (0x05 + (n)) /* n: 1/2/3 */ +#define HDMI_REG_INT_MASK(n) (0x08 + (n)) /* n: 1/2/3 */ + +/* INT1 */ +#define INT_AUD_OVERFLOW BIT(7) +#define INT_RDDC_NOACK BIT(5) +#define INT_DDCFIFO_ERR BIT(4) +#define INT_DDC_BUS_HANG BIT(2) +#define INT_RX_SENSE BIT(1) +#define INT_HPD BIT(0) + +/* INT2 */ +#define INT_VID_UNSTABLE BIT(6) +#define INT_PKTACP BIT(5) +#define INT_PKTNULL BIT(4) +#define INT_PKTGEN BIT(3) +#define INT_KSVLIST_CHK BIT(2) +#define INT_AUTH_DONE BIT(1) +#define INT_AUTH_FAIL BIT(0) + +/* INT3 */ +#define INT_AUD_CTS BIT(6) +#define INT_VSYNC BIT(5) +#define INT_VIDSTABLE BIT(4) +#define INT_PKTMPG BIT(3) +#define INT_PKTGBD BIT(2) +#define INT_PKTAUD BIT(1) +#define INT_PKTAVI BIT(0) + +#define INT_MASK_AUD_CTS BIT(5) +#define INT_MASK_VSYNC BIT(4) +#define INT_MASK_VIDSTABLE BIT(3) +#define INT_MASK_PKTMPG BIT(2) +#define INT_MASK_PKTGBD BIT(1) +#define INT_MASK_PKTAUD BIT(0) + +#define HDMI_REG_INT_CLR(n) (0x0C + (n)) /* n: 0/1 */ + +/* CLR0 */ +#define INT_CLR_PKTACP BIT(7) +#define INT_CLR_PKTNULL BIT(6) +#define INT_CLR_PKTGEN BIT(5) +#define INT_CLR_KSVLIST_CHK BIT(4) +#define INT_CLR_AUTH_DONE BIT(3) +#define INT_CLR_AUTH_FAIL BIT(2) +#define INT_CLR_RXSENSE BIT(1) +#define INT_CLR_HPD BIT(0) + +/* CLR1 */ +#define INT_CLR_VSYNC BIT(7) +#define INT_CLR_VIDSTABLE BIT(6) +#define INT_CLR_PKTMPG BIT(5) +#define INT_CLR_PKTGBD BIT(4) +#define INT_CLR_PKTAUD BIT(3) +#define INT_CLR_PKTAVI BIT(2) +#define INT_CLR_VID_UNSTABLE BIT(0) + +#define HDMI_REG_SYS_STATUS 0x0E +#define INT_ACTIVE BIT(7) +#define HPDETECT BIT(6) +#define RXSENDETECT BIT(5) +#define TXVIDSTABLE BIT(4) +#define CTSINTSTEP 0xC +#define CLR_AUD_CTS BIT(1) +#define INTACTDONE BIT(0) + +#define HDMI_REG_BANK_CTRL 0x0F +#define BANK_SEL(n) ((n) ? 1 : 0) + +/* HDMI System DDC control registers */ +#define HDMI_REG_DDC_MASTER_CTRL 0x10 +#define MASTER_SEL_HOST BIT(0) +#define MASTER_SEL_HDCP 0 + +#define HDMI_REG_DDC_HEADER 0x11 +#define DDC_HDCP_ADDRESS 0x74 + +#define HDMI_REG_DDC_REQOFF 0x12 +#define HDMI_REG_DDC_REQCOUNT 0x13 +#define HDMI_REG_DDC_EDIDSEG 0x14 + +#define HDMI_REG_DDC_CMD 0x15 +#define DDC_CMD_SEQ_BURSTREAD 0x0 +#define DDC_CMD_LINK_CHKREAD 0x2 +#define DDC_CMD_EDID_READ 0x3 +#define DDC_CMD_FIFO_CLR 0x9 +#define DDC_CMD_GEN_SCLCLK 0xA +#define DDC_CMD_ABORT 0xF + +#define HDMI_REG_DDC_STATUS 0x16 +#define DDC_DONE BIT(7) +#define DDC_ACT BIT(6) +#define DDC_NOACK BIT(5) +#define DDC_WAITBUS BIT(4) +#define DDC_ARBILOSE BIT(3) +#define DDC_ERROR (DDC_NOACK | DDC_WAITBUS | DDC_ARBILOSE) +#define DDC_FIFOFULL BIT(2) +#define DDC_FIFOEMPTY BIT(1) + +#define HDMI_DDC_FIFO_SIZE 32 /* bytes */ +#define HDMI_REG_DDC_READFIFO 0x17 +#define HDMI_REG_ROM_STAT 0x1C +#define HDMI_REG_LVDS_PORT 0x1D /* LVDS input ctrl i2c addr */ +#define HDMI_REG_LVDS_PORT_EN 0x1E /* and to enable */ +#define LVDS_INPUT_CTRL_I2C_ADDR 0x33 + +/***********************/ +/* HDMI register bank0 */ +/***********************/ + +/* HDMI clock control registers */ +#define HDMI_REG_CLK_CTRL1 0x59 +#define EN_TXCLK_COUNT BIT(5) +#define VDO_LATCH_EDGE BIT(3) + +/* HDMI AFE registers */ +#define HDMI_REG_AFE_DRV_CTRL 0x61 +#define AFE_DRV_PWD BIT(5) +#define AFE_DRV_RST BIT(4) +#define AFE_DRV_PDRXDET BIT(2) +#define AFE_DRV_TERMON BIT(1) +#define AFE_DRV_ENCAL BIT(0) + +#define HDMI_REG_AFE_XP_CTRL 0x62 +#define AFE_XP_GAINBIT BIT(7) +#define AFE_XP_PWDPLL BIT(6) +#define AFE_XP_ENI BIT(5) +#define AFE_XP_ER0 BIT(4) +#define AFE_XP_RESETB BIT(3) +#define AFE_XP_PWDI BIT(2) +#define AFE_XP_DEI BIT(1) +#define AFE_XP_DER BIT(0) + +#define HDMI_REG_AFE_ISW_CTRL 0x63 +#define AFE_RTERM_SEL BIT(7) +#define AFE_IP_BYPASS BIT(6) +#define AFE_DRV_ISW 0x38 +#define AFE_DRV_ISWK 7 + +#define HDMI_REG_AFE_IP_CTRL 0x64 +#define AFE_IP_GAINBIT BIT(7) +#define AFE_IP_PWDPLL BIT(6) +#define AFE_IP_CKSEL 0x30 +#define AFE_IP_ER0 BIT(3) +#define AFE_IP_RESETB BIT(2) +#define AFE_IP_ENC BIT(1) +#define AFE_IP_EC1 BIT(0) + +/* HDMI input data format registers */ +#define HDMI_REG_INPUT_MODE 0x70 +#define IN_RGB 0x00 +#define IN_YUV422 0x40 +#define IN_YUV444 0x80 + +#define HDMI_REG_TXFIFO_RST 0x71 +#define ENAVMUTERST BIT(0) +#define TXFFRST BIT(1) + +/* HDMI pattern generation SYNC/DE registers */ +#define HDMI_REG_9X(n) (0x90 + (n)) /* n: 0x0 ~ 0xF */ +#define HDMI_REG_AX(n) (0xA0 + (n)) /* n: 0x0 ~ 0xF */ +#define HDMI_REG_B0 0xB0 + +/* HDMI general control registers */ +#define HDMI_REG_HDMI_MODE 0xC0 +#define TX_HDMI_MODE 1 +#define TX_DVI_MODE 0 + +#define HDMI_REG_GCP 0xC1 +#define AVMUTE BIT(0) +#define BLUE_SCR_MUTE BIT(1) +#define NODEF_PHASE BIT(2) +#define PHASE_RESYNC BIT(3) +#define HDMI_COLOR_DEPTH 0x70 +enum { + HDMI_COLOR_DEPTH_DEF = 0x0, /* default as 24bit */ + HDMI_COLOR_DEPTH_24 = 0x40, + HDMI_COLOR_DEPTH_30 = 0x50, + HDMI_COLOR_DEPTH_36 = 0x60, + HDMI_COLOR_DEPTH_48 = 0x70, +}; + +#define HDMI_REG_OESS_CYCLE 0xC3 +#define HDMI_REG_ENCRYPTION 0xC4 /* HDCP */ + +#define HDMI_REG_PKT_SINGLE_CTRL 0xC5 +#define SINGLE_PKT BIT(0) +#define BURST_PKT 0 + +#define HDMI_REG_PKT_GENERAL_CTRL 0xC6 +#define HDMI_REG_NULL_CTRL 0xC9 +#define HDMI_REG_ACP_CTRL 0xCA +#define HDMI_REG_ISRC1_CTRL 0xCB +#define HDMI_REG_ISRC2_CTRL 0xCC +#define HDMI_REG_AVI_INFOFRM_CTRL 0xCD +#define HDMI_REG_AUD_INFOFRM_CTRL 0xCE +#define HDMI_REG_SPD_INFOFRM_CTRL 0xCF +#define HDMI_REG_MPG_INFOFRM_CTRL 0xD0 +#define ENABLE_PKT BIT(0) +#define REPEAT_PKT BIT(1) + +struct it6263 { + struct i2c_client *hdmi_i2c; + struct i2c_client *lvds_i2c; + struct regmap *hdmi_regmap; + struct regmap *lvds_regmap; + struct drm_bridge bridge; + struct drm_connector connector; + bool is_hdmi; + bool split_mode; +}; + +static inline struct it6263 *bridge_to_it6263(struct drm_bridge *bridge) +{ + return container_of(bridge, struct it6263, bridge); +} + +static inline struct it6263 *connector_to_it6263(struct drm_connector *con) +{ + return container_of(con, struct it6263, connector); +} + +static inline void lvds_update_bits(struct it6263 *it6263, unsigned int reg, + unsigned int mask, unsigned int val) +{ + regmap_update_bits(it6263->lvds_regmap, reg, mask, val); +} + +static inline void hdmi_update_bits(struct it6263 *it6263, unsigned int reg, + unsigned int mask, unsigned int val) +{ + regmap_update_bits(it6263->hdmi_regmap, reg, mask, val); +} + +static enum drm_connector_status +it6263_connector_detect(struct drm_connector *connector, bool force) +{ + struct it6263 *it6263 = connector_to_it6263(connector); + unsigned int status; + + regmap_read(it6263->hdmi_regmap, HDMI_REG_SYS_STATUS, &status); + + return (status & HPDETECT) ? connector_status_connected : + connector_status_disconnected; +} + +static const struct drm_connector_funcs it6263_connector_funcs = { + .detect = it6263_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = drm_connector_cleanup, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static int +it6263_read_edid(void *data, u8 *buf, unsigned int block, size_t len) +{ + struct it6263 *it6263 = data; + struct regmap *regmap = it6263->hdmi_regmap; + unsigned long timeout; + unsigned int status, count, val; + unsigned int segment = block >> 1; + unsigned int start = (block % 2) * EDID_LENGTH; + + regmap_write(regmap, HDMI_REG_DDC_MASTER_CTRL, MASTER_SEL_HOST); + regmap_write(regmap, HDMI_REG_DDC_HEADER, DDC_ADDR << 1); + regmap_write(regmap, HDMI_REG_DDC_EDIDSEG, segment); + + while (len) { + /* clear DDC FIFO */ + regmap_write(regmap, HDMI_REG_DDC_CMD, DDC_CMD_FIFO_CLR); + + timeout = jiffies + msecs_to_jiffies(10); + do { + regmap_read(regmap, HDMI_REG_DDC_STATUS, &status); + } while (!(status & DDC_DONE) && time_before(jiffies, timeout)); + + if (!(status & DDC_DONE)) { + dev_err(&it6263->hdmi_i2c->dev, + "failed to clear DDC FIFO\n"); + return -ETIMEDOUT; + } + + count = len > HDMI_DDC_FIFO_SIZE ? HDMI_DDC_FIFO_SIZE : len; + + /* fire the read command */ + regmap_write(regmap, HDMI_REG_DDC_REQOFF, start); + regmap_write(regmap, HDMI_REG_DDC_REQCOUNT, count); + regmap_write(regmap, HDMI_REG_DDC_CMD, DDC_CMD_EDID_READ); + + start += count; + len -= count; + + /* wait for reading done */ + timeout = jiffies + msecs_to_jiffies(250); + do { + regmap_read(regmap, HDMI_REG_DDC_STATUS, &status); + if (status & DDC_ERROR) { + dev_err(&it6263->hdmi_i2c->dev, "DDC error\n"); + return -EIO; + } + } while (!(status & DDC_DONE) && time_before(jiffies, timeout)); + + if (!(status & DDC_DONE)) { + dev_err(&it6263->hdmi_i2c->dev, + "failed to read EDID\n"); + return -ETIMEDOUT; + } + + /* cache to buffer */ + for (; count > 0; count--) { + regmap_read(regmap, HDMI_REG_DDC_READFIFO, &val); + *(buf++) = val; + } + } + + return 0; +} + +static int it6263_get_modes(struct drm_connector *connector) +{ + struct it6263 *it6263 = connector_to_it6263(connector); + struct regmap *regmap = it6263->hdmi_regmap; + u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24; + struct edid *edid; + int num = 0; + int ret; + + regmap_write(regmap, HDMI_REG_DDC_MASTER_CTRL, MASTER_SEL_HOST); + + edid = drm_do_get_edid(connector, it6263_read_edid, it6263); + drm_mode_connector_update_edid_property(connector, edid); + if (edid) { + num = drm_add_edid_modes(connector, edid); + kfree(edid); + } + + ret = drm_display_info_set_bus_formats(&connector->display_info, + &bus_format, 1); + if (ret) + return ret; + + it6263->is_hdmi = drm_detect_hdmi_monitor(edid); + + return num; +} + +enum drm_mode_status it6263_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + if (mode->clock > 150000) + return MODE_CLOCK_HIGH; + + return MODE_OK; +} + +static const struct drm_connector_helper_funcs it6263_connector_helper_funcs = { + .get_modes = it6263_get_modes, + .mode_valid = it6263_mode_valid, +}; + +static void it6263_bridge_disable(struct drm_bridge *bridge) +{ + struct it6263 *it6263 = bridge_to_it6263(bridge); + struct regmap *regmap = it6263->hdmi_regmap; + + /* AV mute */ + hdmi_update_bits(it6263, HDMI_REG_GCP, AVMUTE, AVMUTE); + + if (it6263->is_hdmi) + regmap_write(regmap, HDMI_REG_PKT_GENERAL_CTRL, 0); + + hdmi_update_bits(it6263, HDMI_REG_SW_RST, SOFTV_RST, SOFTV_RST); + regmap_write(regmap, HDMI_REG_AFE_DRV_CTRL, AFE_DRV_RST | AFE_DRV_PWD); +} + +static void it6263_bridge_enable(struct drm_bridge *bridge) +{ + struct it6263 *it6263 = bridge_to_it6263(bridge); + struct regmap *regmap = it6263->hdmi_regmap; + unsigned long timeout; + unsigned int status; + + /* software video reset */ + hdmi_update_bits(it6263, HDMI_REG_SW_RST, SOFTV_RST, SOFTV_RST); + usleep_range(1000, 2000); + hdmi_update_bits(it6263, HDMI_REG_SW_RST, SOFTV_RST, 0); + + timeout = jiffies + msecs_to_jiffies(500); + do { + regmap_read(regmap, HDMI_REG_SYS_STATUS, &status); + } while (!(status & TXVIDSTABLE) && time_before(jiffies, timeout)); + + if (!(status & TXVIDSTABLE)) + dev_warn(&it6263->hdmi_i2c->dev, + "failed to wait for video stable\n"); + + regmap_write(regmap, HDMI_REG_AFE_DRV_CTRL, 0); + + /* AV unmute */ + hdmi_update_bits(it6263, HDMI_REG_GCP, AVMUTE, 0); + + if (it6263->is_hdmi) + regmap_write(regmap, HDMI_REG_PKT_GENERAL_CTRL, + ENABLE_PKT | REPEAT_PKT); +} + +static void it6263_bridge_mode_set(struct drm_bridge *bridge, + struct drm_display_mode *mode, + struct drm_display_mode *adj) +{ + struct it6263 *it6263 = bridge_to_it6263(bridge); + struct regmap *regmap = it6263->hdmi_regmap; + bool pclk_high = adj->clock > 80000 ? true : false; + + regmap_write(regmap, HDMI_REG_HDMI_MODE, + it6263->is_hdmi ? TX_HDMI_MODE : TX_DVI_MODE); + + dev_dbg(&it6263->hdmi_i2c->dev, "%s mode\n", + it6263->is_hdmi ? "HDMI" : "DVI"); + + /* setup AFE */ + regmap_write(regmap, HDMI_REG_AFE_DRV_CTRL, AFE_DRV_RST); + if (pclk_high) + regmap_write(regmap, HDMI_REG_AFE_XP_CTRL, + AFE_XP_GAINBIT | AFE_XP_RESETB); + else + regmap_write(regmap, HDMI_REG_AFE_XP_CTRL, + AFE_XP_ER0 | AFE_XP_RESETB); + regmap_write(regmap, HDMI_REG_AFE_ISW_CTRL, 0x10); + if (pclk_high) + regmap_write(regmap, HDMI_REG_AFE_IP_CTRL, + AFE_IP_GAINBIT | AFE_IP_RESETB); + else + regmap_write(regmap, HDMI_REG_AFE_IP_CTRL, + AFE_IP_ER0 | AFE_IP_RESETB); +} + +static int it6263_bridge_attach(struct drm_bridge *bridge) +{ + struct it6263 *it6263 = bridge_to_it6263(bridge); + struct drm_device *drm = bridge->dev; + int ret; + + if (!drm_core_check_feature(drm, DRIVER_ATOMIC)) { + dev_err(&it6263->hdmi_i2c->dev, + "it6263 driver only copes with atomic updates\n"); + return -ENOTSUPP; + } + + it6263->connector.polled = DRM_CONNECTOR_POLL_CONNECT | + DRM_CONNECTOR_POLL_DISCONNECT; + ret = drm_connector_init(drm, &it6263->connector, + &it6263_connector_funcs, + DRM_MODE_CONNECTOR_HDMIA); + if (ret) { + dev_err(&it6263->hdmi_i2c->dev, + "Failed to initialize connector with drm\n"); + return ret; + } + + drm_connector_helper_add(&it6263->connector, + &it6263_connector_helper_funcs); + drm_mode_connector_attach_encoder(&it6263->connector, bridge->encoder); + + return ret; +} + +static const struct drm_bridge_funcs it6263_bridge_funcs = { + .attach = it6263_bridge_attach, + .mode_set = it6263_bridge_mode_set, + .disable = it6263_bridge_disable, + .enable = it6263_bridge_enable, +}; + +static int it6263_check_chipid(struct it6263 *it6263) +{ + struct device *dev = &it6263->hdmi_i2c->dev; + u8 vendor_id[2], device_id[2]; + int ret; + + ret = regmap_bulk_read(it6263->hdmi_regmap, REG_VENDOR_ID(0), + &vendor_id, 2); + if (ret) { + dev_err(dev, "regmap_bulk_read failed %d\n", ret); + return ret; + } + + if (vendor_id[0] != HDMI_VENDER_ID_LOW || + vendor_id[1] != HDMI_VENDER_ID_HIGH) { + dev_err(dev, + "Invalid hdmi vendor id %02x %02x(expect 0x01 0xca)\n", + vendor_id[0], vendor_id[1]); + return -EINVAL; + } + + ret = regmap_bulk_read(it6263->hdmi_regmap, REG_DEVICE_ID(0), + &device_id, 2); + if (ret) { + dev_err(dev, "regmap_bulk_read failed %d\n", ret); + return ret; + } + + if (device_id[0] != HDMI_DEVICE_ID_LOW || + device_id[1] != HDMI_DEVICE_ID_HIGH) { + dev_err(dev, + "Invalid hdmi device id %02x %02x(expect 0x13 0x76)\n", + device_id[0], device_id[1]); + return -EINVAL; + } + + ret = regmap_bulk_read(it6263->lvds_regmap, REG_VENDOR_ID(0), + &vendor_id, 2); + if (ret) { + dev_err(dev, "regmap_bulk_read failed %d\n", ret); + return ret; + } + + if (vendor_id[0] != LVDS_VENDER_ID_LOW || + vendor_id[1] != LVDS_VENDER_ID_HIGH) { + dev_err(dev, + "Invalid lvds vendor id %02x %02x(expect 0x15 0xca)\n", + vendor_id[0], vendor_id[1]); + return -EINVAL; + } + + ret = regmap_bulk_read(it6263->lvds_regmap, REG_DEVICE_ID(0), + &device_id, 2); + if (ret) { + dev_err(dev, "regmap_bulk_read failed %d\n", ret); + return ret; + } + + if (device_id[0] != LVDS_DEVICE_ID_LOW || + device_id[1] != LVDS_DEVICE_ID_HIGH) { + dev_err(dev, + "Invalid lvds device id %02x %02x(expect 0x61 0x62)\n", + device_id[0], device_id[1]); + return -EINVAL; + } + + return ret; +} + +static void it6263_lvds_reset(struct it6263 *it6263) +{ + /* AFE PLL reset */ + lvds_update_bits(it6263, LVDS_REG_PLL, 0x1, 0x0); + usleep_range(1000, 2000); + lvds_update_bits(it6263, LVDS_REG_PLL, 0x1, 0x1); + + /* pclk reset */ + lvds_update_bits(it6263, LVDS_REG_SW_RST, + SOFT_PCLK_DM_RST, SOFT_PCLK_DM_RST); + usleep_range(1000, 2000); + lvds_update_bits(it6263, LVDS_REG_SW_RST, SOFT_PCLK_DM_RST, 0x0); + + usleep_range(1000, 2000); +} + +static void it6263_lvds_set_interface(struct it6263 *it6263) +{ + /* color depth */ + lvds_update_bits(it6263, LVDS_REG_MODE, LVDS_COLOR_DEPTH, + LVDS_COLOR_DEPTH_24); + + /* jeida mapping */ + lvds_update_bits(it6263, LVDS_REG_MODE, LVDS_OUT_MAP, JEIDA); + + if (it6263->split_mode) { + lvds_update_bits(it6263, LVDS_REG_MODE, DMODE, SPLIT_MODE); + lvds_update_bits(it6263, LVDS_REG_52, BIT(1), BIT(1)); + } else { + lvds_update_bits(it6263, LVDS_REG_MODE, DMODE, SINGLE_MODE); + lvds_update_bits(it6263, LVDS_REG_52, BIT(1), 0); + } +} + +static void it6263_lvds_set_afe(struct it6263 *it6263) +{ + struct regmap *regmap = it6263->lvds_regmap; + + regmap_write(regmap, LVDS_REG_AFE_3E, 0xaa); + regmap_write(regmap, LVDS_REG_AFE_3F, 0x02); + regmap_write(regmap, LVDS_REG_AFE_47, 0xaa); + regmap_write(regmap, LVDS_REG_AFE_48, 0x02); + regmap_write(regmap, LVDS_REG_AFE_4F, 0x11); + + lvds_update_bits(it6263, LVDS_REG_PLL, 0x07, 0); +} + +static void it6263_hdmi_config(struct it6263 *it6263) +{ + regmap_write(it6263->hdmi_regmap, HDMI_REG_INPUT_MODE, IN_RGB); + + hdmi_update_bits(it6263, HDMI_REG_GCP, HDMI_COLOR_DEPTH, + HDMI_COLOR_DEPTH_24); +} + +static const struct regmap_range it6263_hdmi_volatile_ranges[] = { + { .range_min = 0, .range_max = 0x1ff }, +}; + +static const struct regmap_access_table it6263_hdmi_volatile_table = { + .yes_ranges = it6263_hdmi_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(it6263_hdmi_volatile_ranges), +}; + +static const struct regmap_config it6263_hdmi_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .volatile_table = &it6263_hdmi_volatile_table, + .cache_type = REGCACHE_NONE, +}; + +static const struct regmap_range it6263_lvds_volatile_ranges[] = { + { .range_min = 0, .range_max = 0xff }, +}; + +static const struct regmap_access_table it6263_lvds_volatile_table = { + .yes_ranges = it6263_lvds_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(it6263_lvds_volatile_ranges), +}; + +static const struct regmap_config it6263_lvds_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .volatile_table = &it6263_lvds_volatile_table, + .cache_type = REGCACHE_NONE, +}; + +static const struct i2c_board_info it6263_lvds_i2c = { + I2C_BOARD_INFO("it6263_LVDS_i2c", LVDS_INPUT_CTRL_I2C_ADDR), +}; + +static int it6263_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct device_node *np = dev->of_node; + struct it6263 *it6263; + int ret; + + it6263 = devm_kzalloc(dev, sizeof(*it6263), GFP_KERNEL); + if (!it6263) + return -ENOMEM; + + it6263->split_mode = of_property_read_bool(np, "split-mode"); + + it6263->hdmi_i2c = client; + it6263->lvds_i2c = i2c_new_device(client->adapter, &it6263_lvds_i2c); + if (!it6263->lvds_i2c) + return -ENODEV; + + it6263->hdmi_regmap = devm_regmap_init_i2c(client, + &it6263_hdmi_regmap_config); + if (IS_ERR(it6263->hdmi_regmap)) { + ret = PTR_ERR(it6263->hdmi_regmap); + goto unregister_lvds_i2c; + } + + it6263->lvds_regmap = devm_regmap_init_i2c(it6263->lvds_i2c, + &it6263_lvds_regmap_config); + if (IS_ERR(it6263->lvds_regmap)) { + ret = PTR_ERR(it6263->lvds_regmap); + goto unregister_lvds_i2c; + } + + ret = regmap_write(it6263->hdmi_regmap, HDMI_REG_SW_RST, HDMI_RST_ALL); + if (ret) + goto unregister_lvds_i2c; + + usleep_range(1000, 2000); + + ret = regmap_write(it6263->hdmi_regmap, HDMI_REG_LVDS_PORT, + LVDS_INPUT_CTRL_I2C_ADDR << 1); + if (ret) + goto unregister_lvds_i2c; + + ret = regmap_write(it6263->hdmi_regmap, HDMI_REG_LVDS_PORT_EN, 0x01); + if (ret) + goto unregister_lvds_i2c; + + /* select HDMI bank0 */ + ret = regmap_write(it6263->hdmi_regmap, HDMI_REG_BANK_CTRL, + BANK_SEL(0)); + if (ret) + goto unregister_lvds_i2c; + + ret = it6263_check_chipid(it6263); + if (ret) + goto unregister_lvds_i2c; + + it6263_lvds_reset(it6263); + it6263_lvds_set_interface(it6263); + it6263_lvds_set_afe(it6263); + it6263_hdmi_config(it6263); + + it6263->bridge.funcs = &it6263_bridge_funcs; + it6263->bridge.of_node = np; + ret = drm_bridge_add(&it6263->bridge); + if (ret) { + dev_err(dev, "Failed to add drm_bridge\n"); + return ret; + } + + i2c_set_clientdata(client, it6263); + +unregister_lvds_i2c: + i2c_unregister_device(it6263->lvds_i2c); + return ret; +} + +static int it6263_remove(struct i2c_client *client) + +{ + struct it6263 *it6263 = i2c_get_clientdata(client); + + drm_bridge_remove(&it6263->bridge); + i2c_unregister_device(it6263->lvds_i2c); + + return 0; +} + +static const struct of_device_id it6263_dt_ids[] = { + { .compatible = "ite,it6263", }, + { } +}; +MODULE_DEVICE_TABLE(of, it6263_dt_ids); + +static const struct i2c_device_id it6263_i2c_ids[] = { + { "it6263", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, it6263_i2c_ids); + +static struct i2c_driver it6263_driver = { + .probe = it6263_probe, + .remove = it6263_remove, + .driver = { + .name = "it6263", + .of_match_table = it6263_dt_ids, + }, + .id_table = it6263_i2c_ids, +}; +module_i2c_driver(it6263_driver); + +MODULE_AUTHOR("NXP Semiconductor"); +MODULE_DESCRIPTION("ITE Tech. Inc. IT6263 LVDS->HDMI bridge"); +MODULE_LICENSE("GPL"); -- GitLab