diff --git a/drivers/video/tegra/dc/bridge/maxim_gmsl_dp_serializer.c b/drivers/video/tegra/dc/bridge/maxim_gmsl_dp_serializer.c new file mode 100644 index 00000000..637062d2 --- /dev/null +++ b/drivers/video/tegra/dc/bridge/maxim_gmsl_dp_serializer.c @@ -0,0 +1,1352 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * MAXIM DP Serializer driver for MAXIM GMSL Serializers + * + * Copyright (c) 2021-2023, NVIDIA CORPORATION. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_GMSL_DP_SER_REG_4 0x4 +#define MAX_GMSL_DP_SER_REG_4_GMSL_A (1 << 6) +#define MAX_GMSL_DP_SER_REG_4_GMSL_A_PAM4_VAL (1 << 6) +#define MAX_GMSL_DP_SER_REG_4_GMSL_B (1 << 7) +#define MAX_GMSL_DP_SER_REG_4_GMSL_B_PAM4_VAL (1 << 7) + +#define MAX_GMSL_DP_SER_REG_13 0xD + +#define MAX_GMSL_DP_SER_CTRL3 0x13 +#define MAX_GMSL_DP_SER_CTRL3_LOCK_MASK (1 << 3) +#define MAX_GMSL_DP_SER_CTRL3_LOCK_VAL (1 << 3) + +#define MAX_GMSL_DP_SER_INTR2 0x1A +#define MAX_GMSL_DP_SER_REM_ERR_OEN_A_MASK (1 << 4) +#define MAX_GMSL_DP_SER_REM_ERR_OEN_A_VAL (1 << 4) +#define MAX_GMSL_DP_SER_REM_ERR_OEN_B_MASK (1 << 5) +#define MAX_GMSL_DP_SER_REM_ERR_OEN_B_VAL (1 << 5) + +#define MAX_GMSL_DP_SER_INTR3 0x1B +#define MAX_GMSL_DP_SER_REM_ERR_FLAG_A (1 << 4) +#define MAX_GMSL_DP_SER_REM_ERR_FLAG_B (1 << 5) + +#define MAX_GMSL_DP_SER_INTR8 0x20 +#define MAX_GMSL_DP_SER_INTR8_MASK (1 << 0) +#define MAX_GMSL_DP_SER_INTR8_VAL 0x1 + +#define MAX_GMSL_DP_SER_INTR9 0x21 +#define MAX_GMSL_DP_SER_LOSS_OF_LOCK_FLAG (1 << 0) + +#define MAX_GMSL_DP_SER_LINK_CTRL_PHY_A 0x29 +#define MAX_GMSL_DP_SER_LINK_CTRL_A_MASK (1 << 0) + +#define MAX_GMSL_DP_SER_LCTRL0_A 0x28 +#define MAX_GMSL_DP_SER_LCTRL0_B 0x32 +#define MAX_GMSL_DP_SER_LCTRL0_TX_RATE_MASK (3 << 2) +#define MAX_GMSL_DP_SER_LCTRL0_TX_RATE_VAL_3GBPS 0x4 +#define MAX_GMSL_DP_SER_LCTRL0_TX_RATE_VAL_6GBPS 0x8 +#define MAX_GMSL_DP_SER_LCTRL0_TX_RATE_VAL_12GBPS 0xC + +#define MAX_GMSL_DP_SER_LCTRL2_A 0x2A +#define MAX_GMSL_DP_SER_LCTRL2_B 0x34 +#define MAX_GMSL_DP_SER_LCTRL2_LOCK_MASK (1 << 0) +#define MAX_GMSL_DP_SER_LCTRL2_LOCK_VAL 0x1 + +#define MAX_GMSL_DP_SER_LINK_CTRL_PHY_B 0x33 +#define MAX_GMSL_DP_SER_LINK_CTRL_B_MASK (1 << 0) + +#define MAX_GMSL_DP_SER_TX0_LINK_A 0x50 +#define MAX_GMSL_DP_SER_TX0_LINK_B 0x60 +#define MAX_GMSL_DP_SER_TX0_FEC_ENABLE_MASK (1 << 1) +#define MAX_GMSL_DP_SER_TX0_FEC_ENABLE_VAL 0x2 + +#define MAX_GMSL_DP_SER_VID_TX_X 0x100 +#define MAX_GMSL_DP_SER_VID_TX_Y 0x110 +#define MAX_GMSL_DP_SER_VID_TX_Z 0x120 +#define MAX_GMSL_DP_SER_VID_TX_U 0x130 +#define MAX_GMSL_DP_SER_ENABLE_LINK_A 0x0 +#define MAX_GMSL_DP_SER_ENABLE_LINK_B 0x1 +#define MAX_GMSL_DP_SER_ENABLE_LINK_AB 0x2 + +#define MAX_GMSL_DP_SER_VID_TX_MASK (1 << 0) +#define MAX_GMSL_DP_SER_VID_TX_LINK_MASK (3 << 1) +#define MAX_GMSL_DP_SER_LINK_SEL_SHIFT_VAL 0x1 + +#define MAX_GMSL_DP_SER_PHY_EDP_0_CTRL0_B0 0x6064 +#define MAX_GMSL_DP_SER_PHY_EDP_0_CTRL0_B1 0x6065 +#define MAX_GMSL_DP_SER_PHY_EDP_1_CTRL0_B0 0x6164 +#define MAX_GMSL_DP_SER_PHY_EDP_1_CTRL0_B1 0x6165 +#define MAX_GMSL_DP_SER_PHY_EDP_2_CTRL0_B0 0x6264 +#define MAX_GMSL_DP_SER_PHY_EDP_2_CTRL0_B1 0x6265 +#define MAX_GMSL_DP_SER_PHY_EDP_3_CTRL0_B0 0x6364 +#define MAX_GMSL_DP_SER_PHY_EDP_3_CTRL0_B1 0x6365 + +#define MAX_GMSL_DP_SER_DPRX_TRAIN 0x641A +#define MAX_GMSL_DP_SER_DPRX_TRAIN_STATE_MASK (0xF << 4) +#define MAX_GMSL_DP_SER_DPRX_TRAIN_STATE_VAL 0xF0 + +#define MAX_GMSL_DP_SER_LINK_ENABLE 0x7000 +#define MAX_GMSL_DP_SER_LINK_ENABLE_MASK (1 << 0) + +#define MAX_GMSL_DP_SER_MISC_CONFIG_B1 0x7019 +#define MAX_GMSL_DP_SER_MISC_CONFIG_B1_MASK (1 << 0) +#define MAX_GMSL_DP_SER_MISC_CONFIG_B1_VAL 0x1 + +#define MAX_GMSL_DP_SER_HPD_INTERRUPT_MASK 0x702D +#define MAX_GMSL_DP_SER_HPD_INTERRUPT_VAL 0x7C + +#define MAX_GMSL_DP_SER_MAX_LINK_COUNT 0x7070 +#define MAX_GMSL_DP_SER_MAX_LINK_RATE 0x7074 + +#define MAX_GMSL_DP_SER_LOCAL_EDID 0x7084 + +#define MAX_GMSL_DP_SER_I2C_SPEED_CAPABILITY 0x70A4 +#define MAX_GMSL_DP_SER_I2C_SPEED_CAPABILITY_MASK (0x3F << 0) +#define MAX_GMSL_DP_SER_I2C_SPEED_CAPABILITY_100KBPS 0x8 + +#define MAX_GMSL_DP_SER_MST_PAYLOAD_ID_0 0x7904 +#define MAX_GMSL_DP_SER_MST_PAYLOAD_ID_1 0x7908 +#define MAX_GMSL_DP_SER_MST_PAYLOAD_ID_2 0x790C +#define MAX_GMSL_DP_SER_MST_PAYLOAD_ID_3 0x7910 + +#define MAX_GMSL_DP_SER_TX3_0 0xA3 +#define MAX_GMSL_DP_SER_TX3_1 0xA7 +#define MAX_GMSL_DP_SER_TX3_2 0xAB +#define MAX_GMSL_DP_SER_TX3_3 0xAF + +#define MAX_GMSL_DP_SER_INTERNAL_CRC_X 0x449 +#define MAX_GMSL_DP_SER_INTERNAL_CRC_Y 0x549 +#define MAX_GMSL_DP_SER_INTERNAL_CRC_Z 0x649 +#define MAX_GMSL_DP_SER_INTERNAL_CRC_U 0x749 + +#define MAX_GMSL_DP_SER_INTERNAL_CRC_ENABLE 0x9 +#define MAX_GMSL_DP_SER_INTERNAL_CRC_ERR_DET 0x4 +#define MAX_GMSL_DP_SER_INTERNAL_CRC_ERR_INJ 0x10 + +/* XY Dual View Configuration Registers */ +#define MAX_GMSL_DP_SER_ASYM_14_X 0X04CE +#define MAX_GMSL_DP_SER_ASYM_14_Y 0X05CE +#define MAX_GMSL_DP_SER_ASYM_15_X 0X04CF +#define MAX_GMSL_DP_SER_ASYM_15_Y 0X05CF +#define MAX_GMSL_DP_SER_ASYM_17_X 0X04D1 +#define MAX_GMSL_DP_SER_ASYM_17_Y 0X05D1 + +/* Pipe X Superframe Registers, each next PIPE register at 0x100 offset */ +#define MAX_GMSL_DP_SER_X_M_L 0X04C0 +#define MAX_GMSL_DP_SER_X_M_M 0X04C1 +#define MAX_GMSL_DP_SER_X_M_H 0X04C2 +#define MAX_GMSL_DP_SER_X_N_L 0X04C3 +#define MAX_GMSL_DP_SER_X_N_M 0X04C4 +#define MAX_GMSL_DP_SER_X_N_H 0X04C5 +#define MAX_GMSL_DP_SER_X_X_OFFSET_L 0X04C6 +#define MAX_GMSL_DP_SER_X_X_OFFSET_H 0X04C7 +#define MAX_GMSL_DP_SER_X_X_MAX_L 0X04C8 +#define MAX_GMSL_DP_SER_X_X_MAX_H 0X04C9 +#define MAX_GMSL_DP_SER_X_Y_MAX_L 0X04CA +#define MAX_GMSL_DP_SER_X_Y_MAX_H 0X04CB +#define MAX_GMSL_DP_SER_X_VS_DLY_L 0X04D8 +#define MAX_GMSL_DP_SER_X_VS_DLY_M 0X04D9 +#define MAX_GMSL_DP_SER_X_VS_DLY_H 0X04DA +#define MAX_GMSL_DP_SER_X_VS_HIGH_L 0X04DB +#define MAX_GMSL_DP_SER_X_VS_HIGH_M 0X04DC +#define MAX_GMSL_DP_SER_X_VS_HIGH_H 0X04DD +#define MAX_GMSL_DP_SER_X_VS_LOW_L 0X04DE +#define MAX_GMSL_DP_SER_X_VS_LOW_M 0X04DF +#define MAX_GMSL_DP_SER_X_VS_LOW_H 0X04E0 +#define MAX_GMSL_DP_SER_X_HS_DLY_L 0X04E1 +#define MAX_GMSL_DP_SER_X_HS_DLY_M 0X04E2 +#define MAX_GMSL_DP_SER_X_HS_DLY_H 0X04E3 +#define MAX_GMSL_DP_SER_X_HS_HIGH_L 0X04E4 +#define MAX_GMSL_DP_SER_X_HS_HIGH_H 0X04E5 +#define MAX_GMSL_DP_SER_X_HS_LOW_L 0X04E6 +#define MAX_GMSL_DP_SER_X_HS_LOW_H 0X04E7 +#define MAX_GMSL_DP_SER_X_HS_CNT_L 0X04E8 +#define MAX_GMSL_DP_SER_X_HS_CNT_H 0X04E9 +#define MAX_GMSL_DP_SER_X_HS_LLOW_L 0X04EA +#define MAX_GMSL_DP_SER_X_HS_LLOW_M 0X04EB +#define MAX_GMSL_DP_SER_X_HS_LLOW_H 0X04EC +#define MAX_GMSL_DP_SER_X_DE_DLY_L 0X04ED +#define MAX_GMSL_DP_SER_X_DE_DLY_M 0X04EE +#define MAX_GMSL_DP_SER_X_DE_DLY_H 0X04EF +#define MAX_GMSL_DP_SER_X_DE_HIGH_L 0X04F0 +#define MAX_GMSL_DP_SER_X_DE_HIGH_H 0X04F1 +#define MAX_GMSL_DP_SER_X_DE_LOW_L 0X04F2 +#define MAX_GMSL_DP_SER_X_DE_LOW_H 0X04F3 +#define MAX_GMSL_DP_SER_X_DE_CNT_L 0X04F4 +#define MAX_GMSL_DP_SER_X_DE_CNT_H 0X04F5 +#define MAX_GMSL_DP_SER_X_DE_LLOW_L 0X04F6 +#define MAX_GMSL_DP_SER_X_DE_LLOW_M 0X04F7 +#define MAX_GMSL_DP_SER_X_DE_LLOW_H 0X04F8 +#define MAX_GMSL_DP_SER_X_LUT_TEMPLATE 0x04CD + +#define MAX_GMSL_ARRAY_SIZE 4 + +#define MAX_GMSL_LINKS 2 +#define MAX_GMSL_LINK_A 0 +#define MAX_GMSL_LINK_B 1 + +#define MAX_GMSL_PIPE_X 0 +#define MAX_GMSL_PIPE_Y 1 +#define MAX_GMSL_PIPE_Z 2 +#define MAX_GMSL_PIPE_U 3 + +#define MAX_GMSL_DP_SER_DUAL_VIEW_ZY_OFFSET 0x200 +#define MAX_GMSL_DP_SER_PIPE_SUBIMAGE_OFFSET 0x100 + +struct max_gmsl_dp_ser_source { + struct fwnode_handle *fwnode; +}; + +static const struct regmap_config max_gmsl_dp_ser_i2c_regmap = { + .reg_bits = 16, + .val_bits = 8, +}; + +struct max_gmsl_subimage_timing { + bool enable; + u32 superframe_group; + u32 x_start; + u32 h_active; + u32 h_front_porch; + u32 h_back_porch; + u32 h_sync_len; + u32 h_total; + u32 y_start; + u32 v_active; + u32 v_front_porch; + u32 v_back_porch; + u32 v_sync_len; + u32 v_total; +}; + +struct max_gmsl_subimage_attr { + u32 m, n, x_offset, x_max, y_max; + u32 vs_dly, vs_high, vs_low; + u32 hs_dly, hs_high, hs_low, hs_cnt, hs_llow; + u32 de_dly, de_high, de_low, de_cnt, de_llow; + u8 lut_template; +}; + +struct superframe_info { + struct max_gmsl_subimage_timing subimage_timing[MAX_GMSL_ARRAY_SIZE]; + struct max_gmsl_subimage_attr subimage_attr[MAX_GMSL_ARRAY_SIZE]; +}; + +struct max_gmsl_dp_ser_priv { + struct i2c_client *client; + struct gpio_desc *gpiod_pwrdn; + u8 dprx_lane_count; + u8 dprx_link_rate; + struct mutex mutex; + struct regmap *regmap; + int ser_errb; + unsigned int ser_irq; + bool enable_mst; + u32 mst_payload_ids[MAX_GMSL_ARRAY_SIZE]; + u32 gmsl_stream_ids[MAX_GMSL_ARRAY_SIZE]; + u32 gmsl_link_select[MAX_GMSL_ARRAY_SIZE]; + bool link_is_enabled[MAX_GMSL_LINKS]; + u32 enable_gmsl3[MAX_GMSL_LINKS]; + u32 enable_gmsl_fec[MAX_GMSL_LINKS]; + struct superframe_info superframe; +}; + +static int max_gmsl_dp_ser_read(struct max_gmsl_dp_ser_priv *priv, int reg) +{ + int ret, val = 0; + + ret = regmap_read(priv->regmap, reg, &val); + if (ret < 0) + dev_err(&priv->client->dev, + "%s: register 0x%02x read failed (%d)\n", + __func__, reg, ret); + return val; +} + +static int max_gmsl_dp_ser_write(struct max_gmsl_dp_ser_priv *priv, u32 reg, u8 val) +{ + int ret; + + ret = regmap_write(priv->regmap, reg, val); + if (ret < 0) + dev_err(&priv->client->dev, + "%s: register 0x%02x write failed (%d)\n", + __func__, reg, ret); + + return ret; +} + +/* static api to update given value */ +static inline void max_gmsl_dp_ser_update(struct max_gmsl_dp_ser_priv *priv, + u32 reg, u32 mask, u8 val) +{ + u8 update_val; + + update_val = max_gmsl_dp_ser_read(priv, reg); + update_val = ((update_val & (~mask)) | (val & mask)); + max_gmsl_dp_ser_write(priv, reg, update_val); +} + +static void max_gmsl_dp_ser_mst_setup(struct max_gmsl_dp_ser_priv *priv) +{ + int i; + static const int max_mst_payload_id_reg[] = { + MAX_GMSL_DP_SER_MST_PAYLOAD_ID_0, + MAX_GMSL_DP_SER_MST_PAYLOAD_ID_1, + MAX_GMSL_DP_SER_MST_PAYLOAD_ID_2, + MAX_GMSL_DP_SER_MST_PAYLOAD_ID_3, + }; + static const int max_gmsl_stream_id_regs[] = { + MAX_GMSL_DP_SER_TX3_0, + MAX_GMSL_DP_SER_TX3_1, + MAX_GMSL_DP_SER_TX3_2, + MAX_GMSL_DP_SER_TX3_3, + }; + + /* enable MST by programming MISC_CONFIG_B1 reg */ + max_gmsl_dp_ser_update(priv, MAX_GMSL_DP_SER_MISC_CONFIG_B1, + MAX_GMSL_DP_SER_MISC_CONFIG_B1_MASK, + MAX_GMSL_DP_SER_MISC_CONFIG_B1_VAL); + + /* program MST payload IDs */ + for (i = 0; i < ARRAY_SIZE(priv->mst_payload_ids); i++) { + max_gmsl_dp_ser_write(priv, max_mst_payload_id_reg[i], + priv->mst_payload_ids[i]); + } + + /* Program stream IDs */ + for (i = 0; i < ARRAY_SIZE(priv->gmsl_stream_ids); i++) { + max_gmsl_dp_ser_write(priv, max_gmsl_stream_id_regs[i], + priv->gmsl_stream_ids[i]); + } +} + +static void max_gmsl_dp_ser_setup(struct max_gmsl_dp_ser_priv *priv) +{ + int i; + u32 gmsl_link_select_value = 0; + static const int max_gmsl_ser_vid_tx_regs[] = { + MAX_GMSL_DP_SER_VID_TX_X, + MAX_GMSL_DP_SER_VID_TX_Y, + MAX_GMSL_DP_SER_VID_TX_Z, + MAX_GMSL_DP_SER_VID_TX_U, + }; + + static const int max_gmsl_ser_tx0_link_regs[] = { + MAX_GMSL_DP_SER_TX0_LINK_A, + MAX_GMSL_DP_SER_TX0_LINK_B, + }; + + static const int max_gmsl_ser_lctrl0_regs[] = { + MAX_GMSL_DP_SER_LCTRL0_A, + MAX_GMSL_DP_SER_LCTRL0_B, + }; + + static const int max_gmsl_ser_reg4_mask[] = { + MAX_GMSL_DP_SER_REG_4_GMSL_A, + MAX_GMSL_DP_SER_REG_4_GMSL_B, + }; + + static const int max_gmsl_ser_reg4_value[] = { + MAX_GMSL_DP_SER_REG_4_GMSL_A_PAM4_VAL, + MAX_GMSL_DP_SER_REG_4_GMSL_B_PAM4_VAL, + }; + + /* + * Just enable "Loss of Training" and "Register control" events. + * Mask rest of event which can trigger HPD_IRQ. + */ + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_HPD_INTERRUPT_MASK, + MAX_GMSL_DP_SER_HPD_INTERRUPT_VAL); + + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_PHY_EDP_0_CTRL0_B0, 0x0f); + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_PHY_EDP_0_CTRL0_B1, 0x0f); + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_PHY_EDP_1_CTRL0_B0, 0x0f); + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_PHY_EDP_1_CTRL0_B1, 0x0f); + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_PHY_EDP_2_CTRL0_B0, 0x0f); + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_PHY_EDP_2_CTRL0_B1, 0x0f); + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_PHY_EDP_3_CTRL0_B0, 0x0f); + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_PHY_EDP_3_CTRL0_B1, 0x0f); + + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_LOCAL_EDID, 0x1); + + /* Disable MST Mode */ + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_MISC_CONFIG_B1, 0x0); + + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_MAX_LINK_RATE, + priv->dprx_link_rate); + + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_MAX_LINK_COUNT, + priv->dprx_lane_count); + + for (i = 0; i < MAX_GMSL_ARRAY_SIZE; i++) { + gmsl_link_select_value = (priv->gmsl_link_select[i] << + MAX_GMSL_DP_SER_LINK_SEL_SHIFT_VAL); + max_gmsl_dp_ser_update(priv, max_gmsl_ser_vid_tx_regs[i], + MAX_GMSL_DP_SER_VID_TX_LINK_MASK, + gmsl_link_select_value); + } + + max_gmsl_dp_ser_update(priv, MAX_GMSL_DP_SER_I2C_SPEED_CAPABILITY, + MAX_GMSL_DP_SER_I2C_SPEED_CAPABILITY_MASK, + MAX_GMSL_DP_SER_I2C_SPEED_CAPABILITY_100KBPS); + + for (i = 0; i < MAX_GMSL_LINKS; i++) { + if (!priv->link_is_enabled[i]) + continue; + + if (priv->enable_gmsl_fec[i] || priv->enable_gmsl3[i]) { + max_gmsl_dp_ser_update(priv, max_gmsl_ser_tx0_link_regs[i], + MAX_GMSL_DP_SER_TX0_FEC_ENABLE_MASK, + MAX_GMSL_DP_SER_TX0_FEC_ENABLE_VAL); + } + + if (priv->enable_gmsl3[i]) { + max_gmsl_dp_ser_update(priv, max_gmsl_ser_lctrl0_regs[i], + MAX_GMSL_DP_SER_LCTRL0_TX_RATE_MASK, + MAX_GMSL_DP_SER_LCTRL0_TX_RATE_VAL_12GBPS); + + max_gmsl_dp_ser_update(priv, MAX_GMSL_DP_SER_REG_4, + max_gmsl_ser_reg4_mask[i], + max_gmsl_ser_reg4_value[i]); + } + } + + if (priv->enable_mst) + max_gmsl_dp_ser_mst_setup(priv); +} + +static bool max_gmsl_dp_ser_check_dups(u32 *ids) +{ + int i = 0, j = 0; + + /* check if IDs are unique */ + for (i = 0; i < MAX_GMSL_ARRAY_SIZE; i++) { + for (j = i + 1; j < MAX_GMSL_ARRAY_SIZE; j++) { + if (ids[i] == ids[j]) + return false; + } + } + + return true; +} + +static void max_gmsl_detect_internal_crc_error(struct max_gmsl_dp_ser_priv *priv, + struct device *dev) +{ + int i, ret = 0; + + static const int max_gmsl_internal_crc_regs[] = { + MAX_GMSL_DP_SER_INTERNAL_CRC_X, + MAX_GMSL_DP_SER_INTERNAL_CRC_Y, + MAX_GMSL_DP_SER_INTERNAL_CRC_Z, + MAX_GMSL_DP_SER_INTERNAL_CRC_U, + }; + + for (i = 0; i < MAX_GMSL_ARRAY_SIZE; i++) { + ret = max_gmsl_dp_ser_read(priv, max_gmsl_internal_crc_regs[i]); + /* Reading register will clear the detect bit */ + if ((ret & MAX_GMSL_DP_SER_INTERNAL_CRC_ERR_DET) != 0U) { + dev_err(dev, "%s: INTERNAL CRC video error detected at pipe %d\n", __func__, i); + if ((ret & MAX_GMSL_DP_SER_INTERNAL_CRC_ERR_INJ) != 0U) { + /* CRC error is forcefuly injected, disable it */ + ret = ret & (~MAX_GMSL_DP_SER_INTERNAL_CRC_ERR_INJ); + max_gmsl_dp_ser_write(priv, max_gmsl_internal_crc_regs[i], ret); + } + } + } +} + +/* + * This function is responsible for detecting ANY remote deserializer + * errors. Note that the main error that we're interested in today is + * any video line CRC error reported by the deserializer. + */ +static void max_gmsl_detect_remote_error(struct max_gmsl_dp_ser_priv *priv, + struct device *dev) +{ + int ret = 0; + + ret = max_gmsl_dp_ser_read(priv, MAX_GMSL_DP_SER_INTR3); + + if (priv->link_is_enabled[MAX_GMSL_LINK_A]) { + if ((ret & MAX_GMSL_DP_SER_REM_ERR_FLAG_A) != 0) + dev_err(dev, "%s: Remote deserializer error detected on Link A\n", __func__); + } + + if (priv->link_is_enabled[MAX_GMSL_LINK_B]) { + if ((ret & MAX_GMSL_DP_SER_REM_ERR_FLAG_B) != 0) + dev_err(dev, "%s: Remote deserializer error detected on Link B\n", __func__); + } +} + +static irqreturn_t max_gsml_dp_ser_irq_handler(int irq, void *dev_id) +{ + struct max_gmsl_dp_ser_priv *priv = dev_id; + int ret = 0; + struct device *dev = &priv->client->dev; + + ret = max_gmsl_dp_ser_read(priv, MAX_GMSL_DP_SER_INTR9); + if (ret & MAX_GMSL_DP_SER_LOSS_OF_LOCK_FLAG) + dev_dbg(dev, "%s: Fault due to GMSL Link Loss\n", __func__); + + /* Detect internal CRC errors inside serializer */ + max_gmsl_detect_internal_crc_error(priv, dev); + + /* Detect remote error across GMSL link */ + max_gmsl_detect_remote_error(priv, dev); + + return IRQ_HANDLED; +} + +static void max_gmsl_dp_ser_program_dual_view_for_superframe_group(struct device *dev, u32 pipe_id) +{ + struct i2c_client *client = to_i2c_client(dev); + struct max_gmsl_dp_ser_priv *priv = i2c_get_clientdata(client); + u32 offset = 0; + + /* Z-U registers offset shifted by 0x200 */ + if (pipe_id == MAX_GMSL_PIPE_Z) + offset = MAX_GMSL_DP_SER_DUAL_VIEW_ZY_OFFSET; + + /* + * TODO: Get formula and sequence to calculate these regiters. + * As of now, just hard code these settings for symmetric dual view. + * These needs to be program in exact sequence and same register + * is modified with different values. As of now, data sheet does not + * have details for all fields of these registers. + */ + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_ASYM_14_Y + offset, 0x37); + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_ASYM_17_X + offset, 0xF8); + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_ASYM_17_Y + offset, 0xF8); + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_ASYM_15_X + offset, 0xBF); + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_ASYM_15_Y + offset, 0xBF); + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_ASYM_17_X + offset, 0xFC); + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_ASYM_17_Y + offset, 0xFC); + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_ASYM_14_X + offset, 0x2F); + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_ASYM_14_X + offset, 0x0F); + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_ASYM_14_Y + offset, 0x27); + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_ASYM_14_Y + offset, 0x07); +} + +static void max_gmsl_dp_ser_program_subimage_timings(struct device *dev, u32 pipe_id) +{ + struct i2c_client *client = to_i2c_client(dev); + struct max_gmsl_dp_ser_priv *priv = i2c_get_clientdata(client); + u32 offset = pipe_id * MAX_GMSL_DP_SER_PIPE_SUBIMAGE_OFFSET; + + // Asymmetric dual-view total pixel count per frame in sub-image + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_X_M_L + offset, (priv->superframe.subimage_attr[pipe_id].m & 0xFF)); + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_X_M_M + offset, (priv->superframe.subimage_attr[pipe_id].m & 0xFF00) >> 8); + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_X_M_H + offset, (priv->superframe.subimage_attr[pipe_id].m & 0xFF0000) >> 16); + + // Asymmetric dual-view total pixel count per frame for superframe + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_X_N_L + offset, (priv->superframe.subimage_attr[pipe_id].n & 0xFF)); + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_X_N_M + offset, (priv->superframe.subimage_attr[pipe_id].n & 0xFF00) >> 8); + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_X_N_H + offset, (priv->superframe.subimage_attr[pipe_id].n & 0xFF0000) >> 16); + + // Number of pixels to skip before writing when receiving a new line + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_X_X_OFFSET_L + offset, (priv->superframe.subimage_attr[pipe_id].x_offset & 0xFF)); + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_X_X_OFFSET_H + offset, (priv->superframe.subimage_attr[pipe_id].x_offset & 0xFF00) >> 8); + + // Index of the last pixel of sub-image in the superframe + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_X_X_MAX_L + offset, (priv->superframe.subimage_attr[pipe_id].x_max & 0xFF)); + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_X_X_MAX_H + offset, (priv->superframe.subimage_attr[pipe_id].x_max & 0xFF00) >> 8); + + // Index of the last line of sub-image in the superframe + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_X_Y_MAX_L + offset, (priv->superframe.subimage_attr[pipe_id].y_max & 0xFF)); + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_X_Y_MAX_H + offset, (priv->superframe.subimage_attr[pipe_id].y_max & 0xFF00) >> 8); + + // VS delay in terms of PCLK cycles. The output VS is delayed by VS_DELAY cycles from the start. + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_X_VS_DLY_L + offset, (priv->superframe.subimage_attr[pipe_id].vs_dly & 0xFF)); + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_X_VS_DLY_M + offset, (priv->superframe.subimage_attr[pipe_id].vs_dly & 0xFF00) >> 8); + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_X_VS_DLY_H + offset, (priv->superframe.subimage_attr[pipe_id].vs_dly & 0xFF0000) >> 16); + + // VS high period in terms of PCLK + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_X_VS_HIGH_L + offset, (priv->superframe.subimage_attr[pipe_id].vs_high & 0xFF)); + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_X_VS_HIGH_M + offset, (priv->superframe.subimage_attr[pipe_id].vs_high & 0xFF00) >> 8); + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_X_VS_HIGH_H + offset, (priv->superframe.subimage_attr[pipe_id].vs_high & 0xFF0000) >> 16); + + // VS low period in terms of PCLK cycles + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_X_VS_LOW_L + offset, (priv->superframe.subimage_attr[pipe_id].vs_low & 0xFF)); + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_X_VS_LOW_M + offset, (priv->superframe.subimage_attr[pipe_id].vs_low & 0xFF00) >> 8); + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_X_VS_LOW_H + offset, (priv->superframe.subimage_attr[pipe_id].vs_low & 0xFF0000) >> 16); + + // HS delay in terms of PCLK cycles The output HS is delayed by HS_DELAY cycles from the start. + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_X_HS_DLY_L + offset, (priv->superframe.subimage_attr[pipe_id].hs_dly & 0xFF)); + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_X_HS_DLY_M + offset, (priv->superframe.subimage_attr[pipe_id].hs_dly & 0xFF00) >> 8); + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_X_HS_DLY_H + offset, (priv->superframe.subimage_attr[pipe_id].hs_dly & 0xFF0000) >> 16); + + // HS high period in terms of PCLK cycles + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_X_HS_HIGH_L + offset, (priv->superframe.subimage_attr[pipe_id].hs_high & 0xFF)); + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_X_HS_HIGH_H + offset, (priv->superframe.subimage_attr[pipe_id].hs_high & 0xFF00) >> 8); + + // HS low period in terms of PCLK cycles + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_X_HS_LOW_L + offset, (priv->superframe.subimage_attr[pipe_id].hs_low & 0xFF)); + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_X_HS_LOW_H + offset, (priv->superframe.subimage_attr[pipe_id].hs_low & 0xFF00) >> 8); + + // HS pulses per frame + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_X_HS_CNT_L + offset, (priv->superframe.subimage_attr[pipe_id].hs_cnt & 0xFF)); + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_X_HS_CNT_H + offset, (priv->superframe.subimage_attr[pipe_id].hs_cnt & 0xFF00) >> 8); + + // HS long low period in terms of PCLK cycles + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_X_HS_LLOW_L + offset, (priv->superframe.subimage_attr[pipe_id].hs_llow & 0xFF)); + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_X_HS_LLOW_M + offset, (priv->superframe.subimage_attr[pipe_id].hs_llow & 0xFF00) >> 8); + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_X_HS_LLOW_H + offset, (priv->superframe.subimage_attr[pipe_id].hs_llow & 0xFF0000) >> 16); + + // DE delay in terms of PCLK cycles The output HS is delayed by HS_DELAY cycles from the start. + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_X_DE_DLY_L + offset, (priv->superframe.subimage_attr[pipe_id].de_dly & 0xFF)); + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_X_DE_DLY_M + offset, (priv->superframe.subimage_attr[pipe_id].de_dly & 0xFF00) >> 8); + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_X_DE_DLY_H + offset, (priv->superframe.subimage_attr[pipe_id].de_dly & 0xFF0000) >> 16); + + // DE high period in terms of PCLK cycles + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_X_DE_HIGH_L + offset, (priv->superframe.subimage_attr[pipe_id].de_high & 0xFF)); + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_X_DE_HIGH_H + offset, (priv->superframe.subimage_attr[pipe_id].de_high & 0xFF00) >> 8); + + // DE low period in terms of PCLK cycles + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_X_DE_LOW_L + offset, (priv->superframe.subimage_attr[pipe_id].de_low & 0xFF)); + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_X_DE_LOW_H + offset, (priv->superframe.subimage_attr[pipe_id].de_low & 0xFF00) >> 8); + + // DE pulses per frame + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_X_DE_CNT_L + offset, (priv->superframe.subimage_attr[pipe_id].de_cnt & 0xFF)); + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_X_DE_CNT_H + offset, (priv->superframe.subimage_attr[pipe_id].de_cnt & 0xFF00) >> 8); + + // DE long low period in terms of PCLK cycles + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_X_DE_LLOW_L + offset, (priv->superframe.subimage_attr[pipe_id].de_llow & 0xFF)); + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_X_DE_LLOW_M + offset, (priv->superframe.subimage_attr[pipe_id].de_llow & 0xFF00) >> 8); + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_X_DE_LLOW_H + offset, (priv->superframe.subimage_attr[pipe_id].de_llow & 0xFF0000) >> 16); + + // LUT Template + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_X_LUT_TEMPLATE + offset, (priv->superframe.subimage_attr[pipe_id].lut_template)); +} + +static int max_gmsl_dp_ser_configure_superframe(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct max_gmsl_dp_ser_priv *priv = i2c_get_clientdata(client); + + /* + * Currently Enforcing X-Y and/or Z-U Dual view. + * TODO: Add support for other combinations of pipe. + */ + if ((priv->superframe.subimage_timing[MAX_GMSL_PIPE_X].superframe_group != priv->superframe.subimage_timing[MAX_GMSL_PIPE_Y].superframe_group) || + (priv->superframe.subimage_timing[MAX_GMSL_PIPE_Z].superframe_group != priv->superframe.subimage_timing[MAX_GMSL_PIPE_U].superframe_group)) { + dev_err(dev, "%s: Invalid superframe-group combination\n", __func__); + return -1; + } + + if (priv->superframe.subimage_timing[MAX_GMSL_PIPE_X].enable && priv->superframe.subimage_timing[MAX_GMSL_PIPE_Y].enable) { + /* X and Y both enabled, program superframe registers */ + max_gmsl_dp_ser_program_dual_view_for_superframe_group(dev, MAX_GMSL_PIPE_X); + max_gmsl_dp_ser_program_subimage_timings(dev, MAX_GMSL_PIPE_X); + max_gmsl_dp_ser_program_subimage_timings(dev, MAX_GMSL_PIPE_Y); + } + + if (priv->superframe.subimage_timing[MAX_GMSL_PIPE_Z].enable && priv->superframe.subimage_timing[MAX_GMSL_PIPE_U].enable) { + /* Z and U both enabled, program superframe registers */ + max_gmsl_dp_ser_program_dual_view_for_superframe_group(dev, MAX_GMSL_PIPE_Z); + max_gmsl_dp_ser_program_subimage_timings(dev, MAX_GMSL_PIPE_Z); + max_gmsl_dp_ser_program_subimage_timings(dev, MAX_GMSL_PIPE_U); + } + + return 0; +} + +static int max_gmsl_dp_ser_init(struct device *dev) +{ + struct max_gmsl_dp_ser_priv *priv; + struct i2c_client *client; + + client = to_i2c_client(dev); + priv = i2c_get_clientdata(client); + + priv->gpiod_pwrdn = devm_gpiod_get_optional(&client->dev, "enable", + GPIOD_OUT_HIGH); + if (IS_ERR(priv->gpiod_pwrdn)) { + dev_err(dev, "%s: gpiopwrdn is not enabled\n", __func__); + return PTR_ERR(priv->gpiod_pwrdn); + } + gpiod_set_consumer_name(priv->gpiod_pwrdn, "max_gmsl_dp_ser-pwrdn"); + + /* Drive PWRDNB pin high to power up the serializer */ + gpiod_set_value_cansleep(priv->gpiod_pwrdn, 1); + + /* Wait ~4ms for powerup to complete */ + usleep_range(4000, 4200); + + /* + * Write RESET_LINK = 1 (for both Phy A, 0x29, and Phy B, 0x33) + * within 10ms + */ + max_gmsl_dp_ser_update(priv, MAX_GMSL_DP_SER_LINK_CTRL_PHY_A, + MAX_GMSL_DP_SER_LINK_CTRL_A_MASK, 0x1); + max_gmsl_dp_ser_update(priv, MAX_GMSL_DP_SER_LINK_CTRL_PHY_B, + MAX_GMSL_DP_SER_LINK_CTRL_B_MASK, 0x1); + + /* + * Disable video output on the GMSL link by setting VID_TX_EN = 0 + * for Pipe X, Y, Z and U + */ + max_gmsl_dp_ser_update(priv, MAX_GMSL_DP_SER_VID_TX_X, + MAX_GMSL_DP_SER_VID_TX_MASK, 0x0); + max_gmsl_dp_ser_update(priv, MAX_GMSL_DP_SER_VID_TX_Y, + MAX_GMSL_DP_SER_VID_TX_MASK, 0x0); + max_gmsl_dp_ser_update(priv, MAX_GMSL_DP_SER_VID_TX_Z, + MAX_GMSL_DP_SER_VID_TX_MASK, 0x0); + max_gmsl_dp_ser_update(priv, MAX_GMSL_DP_SER_VID_TX_U, + MAX_GMSL_DP_SER_VID_TX_MASK, 0x0); + + /* + * Set LINK_ENABLE=0 (0x7000) to force the DP HPD + * pin low to hold off DP link training and + * SOC video + */ + max_gmsl_dp_ser_update(priv, MAX_GMSL_DP_SER_LINK_ENABLE, + MAX_GMSL_DP_SER_LINK_ENABLE_MASK, 0x0); + + max_gmsl_dp_ser_setup(priv); + + /* + * Write RESET_LINK = 0 (for both Phy A, 0x29, and Phy B, 0x33) + * to initiate the GMSL link lock process. + */ + if (priv->link_is_enabled[MAX_GMSL_LINK_A]) + max_gmsl_dp_ser_update(priv, + MAX_GMSL_DP_SER_LINK_CTRL_PHY_A, + MAX_GMSL_DP_SER_LINK_CTRL_A_MASK, + 0x0); + if (priv->link_is_enabled[MAX_GMSL_LINK_B]) + max_gmsl_dp_ser_update(priv, + MAX_GMSL_DP_SER_LINK_CTRL_PHY_B, + MAX_GMSL_DP_SER_LINK_CTRL_B_MASK, + 0x0); + + /* + * Set LINK_ENABLE = 1 (0x7000) to enable SOC DP link training, + * enable SOC video output to the serializer. + */ + max_gmsl_dp_ser_update(priv, MAX_GMSL_DP_SER_LINK_ENABLE, + MAX_GMSL_DP_SER_LINK_ENABLE_MASK, 0x1); + + max_gmsl_dp_ser_read(priv, MAX_GMSL_DP_SER_INTR9); + + if (priv->link_is_enabled[MAX_GMSL_LINK_A]) + max_gmsl_dp_ser_update(priv, MAX_GMSL_DP_SER_INTR2, + MAX_GMSL_DP_SER_REM_ERR_OEN_A_MASK, + MAX_GMSL_DP_SER_REM_ERR_OEN_A_VAL); + if (priv->link_is_enabled[MAX_GMSL_LINK_B]) + max_gmsl_dp_ser_update(priv, MAX_GMSL_DP_SER_INTR2, + MAX_GMSL_DP_SER_REM_ERR_OEN_B_MASK, + MAX_GMSL_DP_SER_REM_ERR_OEN_B_VAL); + + /* enable INTR8.LOSS_OF_LOCK_OEN */ + max_gmsl_dp_ser_update(priv, MAX_GMSL_DP_SER_INTR8, + MAX_GMSL_DP_SER_INTR8_MASK, + MAX_GMSL_DP_SER_INTR8_VAL); + + /* enable internal CRC after link training */ + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_INTERNAL_CRC_X, + MAX_GMSL_DP_SER_INTERNAL_CRC_ENABLE); + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_INTERNAL_CRC_Y, + MAX_GMSL_DP_SER_INTERNAL_CRC_ENABLE); + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_INTERNAL_CRC_Z, + MAX_GMSL_DP_SER_INTERNAL_CRC_ENABLE); + max_gmsl_dp_ser_write(priv, MAX_GMSL_DP_SER_INTERNAL_CRC_U, + MAX_GMSL_DP_SER_INTERNAL_CRC_ENABLE); + + /* configure superframe settings */ + max_gmsl_dp_ser_configure_superframe(dev); + + /* enable video output */ + max_gmsl_dp_ser_update(priv, MAX_GMSL_DP_SER_VID_TX_X, + MAX_GMSL_DP_SER_VID_TX_MASK, 0x1); + max_gmsl_dp_ser_update(priv, MAX_GMSL_DP_SER_VID_TX_Y, + MAX_GMSL_DP_SER_VID_TX_MASK, 0x1); + max_gmsl_dp_ser_update(priv, MAX_GMSL_DP_SER_VID_TX_Z, + MAX_GMSL_DP_SER_VID_TX_MASK, 0x1); + max_gmsl_dp_ser_update(priv, MAX_GMSL_DP_SER_VID_TX_U, + MAX_GMSL_DP_SER_VID_TX_MASK, 0x1); + + return 0; +} + +static int max_gmsl_dp_ser_parse_mst_props(struct i2c_client *client, + struct max_gmsl_dp_ser_priv *priv) +{ + struct device *dev = &priv->client->dev; + struct device_node *ser = dev->of_node; + int err = 0; + bool ret; + + priv->enable_mst = of_property_read_bool(ser, + "enable-mst"); + if (priv->enable_mst) + dev_info(dev, "%s: MST mode enabled\n", __func__); + else + dev_info(dev, "%s: MST mode not enabled\n", __func__); + + if (priv->enable_mst) { + err = of_property_read_variable_u32_array(ser, + "mst-payload-ids", + priv->mst_payload_ids, 1, + ARRAY_SIZE(priv->mst_payload_ids)); + + if (err < 0) { + dev_info(dev, + "%s: MST Payload prop not found or invalid\n", + __func__); + return -EINVAL; + } + + ret = max_gmsl_dp_ser_check_dups(priv->mst_payload_ids); + if (!ret) { + dev_err(dev, "%s: payload IDs are not unique\n", + __func__); + return -EINVAL; + } + + err = of_property_read_variable_u32_array(ser, + "gmsl-stream-ids", + priv->gmsl_stream_ids, 1, + ARRAY_SIZE(priv->gmsl_stream_ids)); + if (err < 0) { + dev_info(dev, + "%s: GMSL Stream ID property not found or invalid\n", + __func__); + return -EINVAL; + } + + ret = max_gmsl_dp_ser_check_dups(priv->gmsl_stream_ids); + if (!ret) { + dev_err(dev, "%s: stream IDs are not unique\n", + __func__); + return -EINVAL; + } + } + + return 0; +} + +static int max_gmsl_dp_ser_parse_gmsl_props(struct i2c_client *client, + struct max_gmsl_dp_ser_priv *priv) +{ + struct device *dev = &priv->client->dev; + struct device_node *ser = dev->of_node; + int err = 0; + + err = of_property_read_variable_u32_array(ser, "enable-gmsl3", + priv->enable_gmsl3, 1, + ARRAY_SIZE(priv->enable_gmsl3)); + if (err < 0) { + if (err == -EINVAL) { + dev_info(dev, + "%s: enable-gmsl3 property not found\n", + __func__); + } else { + dev_err(dev, + "%s: enable-gmsl3 property does not have valid data\n", + __func__); + return -EINVAL; + } + } + + err = of_property_read_variable_u32_array(ser, "enable-gmsl-fec", + priv->enable_gmsl_fec, 1, + ARRAY_SIZE(priv->enable_gmsl_fec)); + if (err < 0) { + if (err == -EINVAL) { + dev_info(dev, + "%s: enable-gmsl-fec property not found\n", + __func__); + } else { + dev_err(dev, + "%s: enable-gmsl-fec property does not have valid data\n", + __func__); + return -EINVAL; + } + } + + return 0; +} + +static void max_gmsl_dp_ser_calc_superframe_pipe_attr(struct i2c_client *client, + struct max_gmsl_dp_ser_priv *priv, + int pipe_id) +{ + //Asymmetric dual-view total pixel count per frame in sub-image + priv->superframe.subimage_attr[pipe_id].m = priv->superframe.subimage_timing[pipe_id].h_total * priv->superframe.subimage_timing[pipe_id].v_total; + + //Asymmetric dual-view total pixel count per frame for superframe + // TODO: Get formula for this value + priv->superframe.subimage_attr[pipe_id].n = 461000; + + //Number of pixels to skip before writing when receiving a new line + priv->superframe.subimage_attr[pipe_id].x_offset = priv->superframe.subimage_timing[pipe_id].x_start; + + //Index of the last pixel of sub-image in the superframe + priv->superframe.subimage_attr[pipe_id].x_max = priv->superframe.subimage_timing[pipe_id].h_active + priv->superframe.subimage_attr[pipe_id].x_offset; + + //Index of the last line of sub-image in the superframe + priv->superframe.subimage_attr[pipe_id].y_max = priv->superframe.subimage_timing[pipe_id].v_active; + + //VS delay in terms of PCLK cycles. The output VS is delayed by VS_DELAY cycles from the start. + priv->superframe.subimage_attr[pipe_id].vs_dly = priv->superframe.subimage_timing[pipe_id].h_total * (priv->superframe.subimage_timing[pipe_id].v_active + priv->superframe.subimage_timing[pipe_id].v_front_porch); + + //VS high period in terms of PCLK + priv->superframe.subimage_attr[pipe_id].vs_high = priv->superframe.subimage_timing[pipe_id].h_total * priv->superframe.subimage_timing[pipe_id].v_sync_len; + + //VS low period in terms of PCLK cycles + //TODO Get formula for this value + priv->superframe.subimage_attr[pipe_id].vs_low = 36000; + + //HS delay in terms of PCLK cycles The output HS is delayed by HS_DELAY cycles from the start. + priv->superframe.subimage_attr[pipe_id].hs_dly = 0; + + //HS high period in terms of PCLK cycles + priv->superframe.subimage_attr[pipe_id].hs_high = priv->superframe.subimage_timing[pipe_id].h_sync_len; + + //HS low period in terms of PCLK cycles + priv->superframe.subimage_attr[pipe_id].hs_low = priv->superframe.subimage_timing[pipe_id].h_active + priv->superframe.subimage_timing[pipe_id].h_front_porch + priv->superframe.subimage_timing[pipe_id].h_back_porch; + + //HS pulses per frame + priv->superframe.subimage_attr[pipe_id].hs_cnt = priv->superframe.subimage_timing[pipe_id].v_total; + + //HS long low period in terms of PCLK cycles + priv->superframe.subimage_attr[pipe_id].hs_llow = 0; + + //DE delay in terms of PCLK cycles The output DE is delayed by DE_DELAY cycles from the start. + priv->superframe.subimage_attr[pipe_id].de_dly = priv->superframe.subimage_timing[pipe_id].h_back_porch + priv->superframe.subimage_timing[pipe_id].h_sync_len; + + //DE high period in terms of PCLK cycles + priv->superframe.subimage_attr[pipe_id].de_high = priv->superframe.subimage_timing[pipe_id].h_active; + + //DE low period in terms of PCLK cycles + priv->superframe.subimage_attr[pipe_id].de_low = priv->superframe.subimage_timing[pipe_id].h_front_porch + priv->superframe.subimage_timing[pipe_id].h_sync_len + priv->superframe.subimage_timing[pipe_id].h_back_porch; + + //Active lines per frame + priv->superframe.subimage_attr[pipe_id].de_cnt = priv->superframe.subimage_timing[pipe_id].v_active; + + //DE long low period in terms of PCLK cycles + //TODO Get formula for this value + priv->superframe.subimage_attr[pipe_id].de_llow = 61944; + + //LUT template + //TODO Get formula for this value + priv->superframe.subimage_attr[pipe_id].lut_template = 20; +} + +static int max_gmsl_dp_ser_parse_superframe_pipe_props(struct i2c_client *client, + struct max_gmsl_dp_ser_priv *priv, + struct device_node *pipe_node, + int pipe_id) +{ + struct device *dev = &priv->client->dev; + int err = 0; + u32 val = 0; + struct device_node *timings_handle; + + err = of_property_read_u32(pipe_node, "superframe-group", &val); + if (err) { + dev_err(dev, "%s: - superframe-group property not found\n", + __func__); + return err; + } + priv->superframe.subimage_timing[pipe_id].superframe_group = val; + + err = of_property_read_u32(pipe_node, "timings-phandle", &val); + if (err) { + dev_err(dev, "%s: - timings-phandle property not found\n", + __func__); + return err; + } + + timings_handle = of_find_node_by_phandle(val); + if (timings_handle == NULL) { + dev_err(dev, "%s: - can not get timings node\n", + __func__); + return -1; + } + + err = of_property_read_u32(timings_handle, "x", &val); + if (err) { + dev_err(dev, "%s: - x property not found\n", + __func__); + return err; + } + priv->superframe.subimage_timing[pipe_id].x_start = val; + + err = of_property_read_u32(timings_handle, "width", &val); + if (err) { + dev_err(dev, "%s: - width property not found\n", + __func__); + return err; + } + priv->superframe.subimage_timing[pipe_id].h_active = val; + + err = of_property_read_u32(timings_handle, "hfront-porch", &val); + if (err) { + dev_err(dev, "%s: - hfront-porch property not found\n", + __func__); + return err; + } + priv->superframe.subimage_timing[pipe_id].h_front_porch = val; + + err = of_property_read_u32(timings_handle, "hback-porch", &val); + if (err) { + dev_err(dev, "%s: - hback-porch property not found\n", + __func__); + return err; + } + priv->superframe.subimage_timing[pipe_id].h_back_porch = val; + + err = of_property_read_u32(timings_handle, "hsync-len", &val); + if (err) { + dev_err(dev, "%s: - hsync-len property not found\n", + __func__); + return err; + } + priv->superframe.subimage_timing[pipe_id].h_sync_len = val; + + priv->superframe.subimage_timing[pipe_id].h_total = priv->superframe.subimage_timing[pipe_id].h_active + priv->superframe.subimage_timing[pipe_id].h_front_porch + priv->superframe.subimage_timing[pipe_id].h_back_porch + priv->superframe.subimage_timing[pipe_id].h_sync_len; + + err = of_property_read_u32(timings_handle, "y", &val); + if (err) { + dev_err(dev, "%s: - y property not found\n", + __func__); + return err; + } + priv->superframe.subimage_timing[pipe_id].y_start = val; + + err = of_property_read_u32(timings_handle, "height", &val); + if (err) { + dev_err(dev, "%s: - height property not found\n", + __func__); + return err; + } + priv->superframe.subimage_timing[pipe_id].v_active = val; + + err = of_property_read_u32(timings_handle, "vfront-porch", &val); + if (err) { + dev_err(dev, "%s: - vfront-porch property not found\n", + __func__); + return err; + } + priv->superframe.subimage_timing[pipe_id].v_front_porch = val; + + err = of_property_read_u32(timings_handle, "vback-porch", &val); + if (err) { + dev_err(dev, "%s: - vback-porch property not found\n", + __func__); + return err; + } + priv->superframe.subimage_timing[pipe_id].v_back_porch = val; + + err = of_property_read_u32(timings_handle, "vsync-len", &val); + if (err) { + dev_err(dev, "%s: - vsync-len property not found\n", + __func__); + return err; + } + priv->superframe.subimage_timing[pipe_id].v_sync_len = val; + + priv->superframe.subimage_timing[pipe_id].v_total = priv->superframe.subimage_timing[pipe_id].v_active + priv->superframe.subimage_timing[pipe_id].v_front_porch + priv->superframe.subimage_timing[pipe_id].v_back_porch + priv->superframe.subimage_timing[pipe_id].v_sync_len; + + return 0; +} + +static void max_gmsl_dp_ser_parse_superframe_props(struct i2c_client *client, + struct max_gmsl_dp_ser_priv *priv) +{ + struct device *dev = &priv->client->dev; + struct device_node *ser = dev->of_node; + int err, i = 0; + struct device_node *superframe_video_timings, *pipe_node; + char pipe_name[MAX_GMSL_ARRAY_SIZE][7] = { "pipe-x", "pipe-y", "pipe-z", "pipe-u"}; + + /* check if superframe timings present in device tree */ + superframe_video_timings = of_find_node_by_name(ser, "superframe-video-timings"); + if (superframe_video_timings == NULL) { + dev_info(dev, "%s: - superframe-video-timings property not found\n", + __func__); + return; + } + + /* get superframe subimage properties for each pipe */ + for (i = 0; i < MAX_GMSL_ARRAY_SIZE; i++) { + pipe_node = of_find_node_by_name(superframe_video_timings, pipe_name[i]); + if (pipe_node) { + err = max_gmsl_dp_ser_parse_superframe_pipe_props(client, priv, pipe_node, i); + if (!err) { + /* pipe have valid properties, calculate attributes */ + priv->superframe.subimage_timing[i].enable = true; + max_gmsl_dp_ser_calc_superframe_pipe_attr(client, priv, i); + } else { + dev_err(dev, "%s: - %s property found but properties are invalid\n", __func__, pipe_name[i]); + } + } else { + dev_info(dev, "%s: - %s property not found\n", __func__, pipe_name[i]); + } + } +} + +static int max_gmsl_dp_ser_parse_dt_props(struct i2c_client *client, + struct max_gmsl_dp_ser_priv *priv) +{ + struct device *dev = &priv->client->dev; + int err = 0; + + err = max_gmsl_dp_ser_parse_mst_props(client, priv); + if (err != 0) { + dev_err(dev, "%s: error parsing MST props\n", __func__); + return -EFAULT; + } + + err = max_gmsl_dp_ser_parse_gmsl_props(client, priv); + if (err != 0) { + dev_err(dev, "%s: error parsing GMSL props\n", __func__); + return -EFAULT; + } + + max_gmsl_dp_ser_parse_superframe_props(client, priv); + + return err; +} + +static int max_gmsl_dp_ser_parse_dt(struct i2c_client *client, + struct max_gmsl_dp_ser_priv *priv) +{ + struct device *dev = &priv->client->dev; + struct device_node *ser = dev->of_node; + int err = 0, i; + u32 val = 0; + + dev_info(dev, "%s: parsing serializer device tree:\n", __func__); + + err = of_property_read_u32(ser, "dprx-lane-count", &val); + if (err) { + if (err == -EINVAL) { + dev_info(dev, "%s: - dprx-lane-count property not found\n", + __func__); + /* default value: 4 */ + priv->dprx_lane_count = 4; + dev_info(dev, "%s: dprx-lane-count set to default val: 4\n", + __func__); + } else { + return err; + } + } else { + /* set dprx-lane-count */ + priv->dprx_lane_count = val; + dev_info(dev, "%s: - dprx-lane-count %i\n", __func__, val); + } + + err = of_property_read_u32(ser, "dprx-link-rate", &val); + if (err) { + if (err == -EINVAL) { + dev_info(dev, "%s: - dprx-link-rate property not found\n", + __func__); + /* default value: 0x1E */ + priv->dprx_link_rate = 0x1E; + dev_info(dev, "%s: dprx-link-rate set to default val: 0x1E\n", + __func__); + } else { + return err; + } + } else { + /* set dprx-link-rate*/ + priv->dprx_link_rate = val; + dev_info(dev, "%s: - dprx-link-rate %i\n", __func__, val); + } + + err = of_property_read_variable_u32_array(ser, "gmsl-link-select", + priv->gmsl_link_select, 1, + ARRAY_SIZE(priv->gmsl_link_select)); + if (err < 0) { + dev_info(dev, + "%s: GMSL Link select property not found or invalid\n", + __func__); + return -EINVAL; + } + + for (i = 0; i < ARRAY_SIZE(priv->gmsl_link_select); i++) { + if ((priv->gmsl_link_select[i] == MAX_GMSL_DP_SER_ENABLE_LINK_A) + || (priv->gmsl_link_select[i] == + MAX_GMSL_DP_SER_ENABLE_LINK_AB)) { + priv->link_is_enabled[MAX_GMSL_LINK_A] = true; + } else if ((priv->gmsl_link_select[i] == MAX_GMSL_DP_SER_ENABLE_LINK_B) + || (priv->gmsl_link_select[i] == + MAX_GMSL_DP_SER_ENABLE_LINK_AB)) { + priv->link_is_enabled[MAX_GMSL_LINK_B] = true; + } else { + dev_info(dev, + "%s: GMSL Link select values are invalid\n", + __func__); + return -EINVAL; + } + } + + err = max_gmsl_dp_ser_parse_dt_props(client, priv); + if (err != 0) { + dev_err(dev, "%s: error parsing DT props\n", __func__); + return -EFAULT; + } + + return 0; +} + +static int max_gmsl_dp_ser_probe(struct i2c_client *client) +{ + struct max_gmsl_dp_ser_priv *priv; + struct device *dev; + struct device_node *ser = client->dev.of_node; + int ret; + + priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL); + if (priv == NULL) + return -ENOMEM; + + mutex_init(&priv->mutex); + + priv->client = client; + i2c_set_clientdata(client, priv); + + priv->regmap = devm_regmap_init_i2c(client, &max_gmsl_dp_ser_i2c_regmap); + if (IS_ERR(priv->regmap)) + return PTR_ERR(priv->regmap); + + dev = &priv->client->dev; + + ret = max_gmsl_dp_ser_read(priv, MAX_GMSL_DP_SER_REG_13); + if (ret != 0) { + dev_info(dev, "%s: MAXIM Serializer detected\n", __func__); + } else { + dev_err(dev, "%s: MAXIM Serializer Not detected\n", __func__); + return -ENODEV; + } + + ret = max_gmsl_dp_ser_parse_dt(client, priv); + if (ret < 0) { + dev_err(dev, "%s: error parsing device tree\n", __func__); + return -EFAULT; + } + + ret = max_gmsl_dp_ser_init(&client->dev); + if (ret < 0) { + dev_err(dev, "%s: dp serializer init failed\n", __func__); + return -EFAULT; + } + + priv->ser_errb = of_get_named_gpio(ser, "ser-errb", 0); + + ret = devm_gpio_request_one(&client->dev, priv->ser_errb, + GPIOF_DIR_IN, "GPIO_MAXIM_SER"); + if (ret < 0) { + dev_err(dev, "%s: GPIO request failed\n ret: %d", + __func__, ret); + return ret; + } + + if (gpio_is_valid(priv->ser_errb)) { + priv->ser_irq = gpio_to_irq(priv->ser_errb); + ret = request_threaded_irq(priv->ser_irq, NULL, + max_gsml_dp_ser_irq_handler, + IRQF_TRIGGER_FALLING + | IRQF_ONESHOT, "SER", priv); + if (ret < 0) { + dev_err(dev, "%s: Unable to register IRQ handler ret: %d\n", + __func__, ret); + return ret; + } + + } + return ret; +} + +#if KERNEL_VERSION(6, 1, 0) <= LINUX_VERSION_CODE +static void max_gmsl_dp_ser_remove(struct i2c_client *client) +#else +static int max_gmsl_dp_ser_remove(struct i2c_client *client) +#endif +{ + struct max_gmsl_dp_ser_priv *priv = i2c_get_clientdata(client); + + i2c_unregister_device(client); + gpiod_set_value_cansleep(priv->gpiod_pwrdn, 0); + +#if KERNEL_VERSION(6, 1, 0) > LINUX_VERSION_CODE + return 0; +#endif +} + +#ifdef CONFIG_PM +static int max_gmsl_dp_ser_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct max_gmsl_dp_ser_priv *priv = i2c_get_clientdata(client); + + /* Drive PWRDNB pin low to power down the serializer */ + gpiod_set_value_cansleep(priv->gpiod_pwrdn, 0); + return 0; +} + +static int max_gmsl_dp_ser_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct max_gmsl_dp_ser_priv *priv = i2c_get_clientdata(client); + int ret = 0; + + /* + Drive PWRDNB pin high to power up the serializer + and initialize all registes + */ + ret = max_gmsl_dp_ser_init(&client->dev); + if (ret < 0) { + dev_err(&priv->client->dev, "%s: dp serializer init failed\n", __func__); + } + return ret; +} + +const struct dev_pm_ops max_gmsl_dp_ser_pm_ops = { + SET_LATE_SYSTEM_SLEEP_PM_OPS( + max_gmsl_dp_ser_suspend, max_gmsl_dp_ser_resume) +}; + +#endif + +static const struct of_device_id max_gmsl_dp_ser_dt_ids[] = { + { .compatible = "maxim,max_gmsl_dp_ser" }, + {}, +}; +MODULE_DEVICE_TABLE(of, max_gmsl_dp_ser_dt_ids); + +static struct i2c_driver max_gmsl_dp_ser_i2c_driver = { + .driver = { + .name = "max_gmsl_dp_ser", + .of_match_table = of_match_ptr(max_gmsl_dp_ser_dt_ids), +#ifdef CONFIG_PM + .pm = &max_gmsl_dp_ser_pm_ops, +#endif + }, + .probe_new = max_gmsl_dp_ser_probe, + .remove = max_gmsl_dp_ser_remove, +}; + +module_i2c_driver(max_gmsl_dp_ser_i2c_driver); + +MODULE_DESCRIPTION("Maxim DP GMSL Serializer Driver"); +MODULE_AUTHOR("Vishwaroop"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/tegra/dc/bridge/ti_fpdlink_dp_serializer.c b/drivers/video/tegra/dc/bridge/ti_fpdlink_dp_serializer.c new file mode 100644 index 00000000..c0874814 --- /dev/null +++ b/drivers/video/tegra/dc/bridge/ti_fpdlink_dp_serializer.c @@ -0,0 +1,1059 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * DS90UH983-Q1 DP to FPD-Link Serializer driver + * + * Copyright (C) 2022, NVIDIA Corporation. All rights reserved. + * + * 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; version 2 of the License. + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Target I2C register address +#define TI_TARGET_ID_0 0x70 +#define TI_TARGET_ID_VAL 0x58 +#define TI_TARGET_ALIAS_ID_0 0x78 +#define TI_TARGET_ALIAS_ID_VAL 0x2C +#define TI_TARGET_DEST_0 0x88 +#define TI_TARGET_DEST_VAL 0x0 +// Reset for SER and PLL +#define TI_RESET_CTL 0x1 +#define TI_RESET_CTL_VAL_SOFT_RESET_SER 0x1 +#define TI_RESET_CTL_VAL_SOFT_RESET_PLL 0x30 +// Indirect Page Access +#define TI_IND_ACC_CTL 0x40 +#define TI_IND_ACC_CTL_SEL_MASK (0x7 << 2) +#define TI_IND_ACC_CTL_SEL_SHIFT 2 +#define TI_IND_ACC_CTL_AUTO_INC_MASK (0x1 << 1) +#define TI_IND_ACC_CTL_READ_MASK (0x0 << 1) +// FPD Port page +#define TI_IND_ACC_PAGE_1 0x1 +// FPD PLL page +#define TI_IND_ACC_PAGE_2 0x2 +#define TI_IND_ACC_PAGE_4 0x4 +#define TI_IND_ACC_PAGE_5 0x5 +#define TI_IND_ACC_PAGE_9 0x9 +#define TI_IND_ACC_PAGE_11 0xb +#define TI_IND_ACC_PAGE_12 0xc +#define TI_IND_ACC_PAGE_14 0xe +#define TI_IND_ACC_ADDR 0x41 +#define TI_IND_ACC_DATA 0x42 +// Page 0 - General +#define TI_GENERAL_CFG 0x7 +#define TI_GENERAL_CFG_SST_MST_MODE_STRP_MASK (0x1 << 1) +#define TI_GENERAL_CFG_I2C_PASS_THROUGH_MASK (0x1 << 3) +#define TI_GENERAL_CFG2 0x2 +#define TI_GENERAL_CFG2_VAL 0xD1 +#define TI_GENERAL_CFG2_DEFAULT 0xDF +#define TI_GENERAL_CFG2_DPRX_EN (0x1 << 4) +#define TI_GENERAL_CFG2_CRC_ERROR_RESET_CLEAR (0x1 << 5) +#define TI_FPD4_CFG 0x5 +#define TI_FPD4_CFG_FPD4_TX_MODE_DUAL 0x28 +#define TI_FPD4_CFG_FPD4_TX_MODE_SINGLE 0x2C +#define TI_FPD4_CFG_FPD4_TX_MODE_IND 0x3C +#define TI_FPD4_PGCFG_VP0 0x29 +#define TI_FPD4_PGCFG_DEFAULT 0x8 +#define TI_FPD3_FIFO_CFG 0x5B +#define TI_FPD3_FIFO_CFG_VAL 0x23 +#define TI_TX_PORT_SEL 0x2D +#define TI_TX_PORT_SEL_VAL_PORT_0 0x1 +#define TI_TX_PORT_SEL_VAL_PORT_1 0x12 +// PAGE 2 PLL +#define TI_PLL_0 0x1B +#define TI_PLL_1 0x5B +#define TI_PLL_DISABLE_VAL 0x1 +#define TI_PLL_DISABLE_SHIFT 0x3 +#define TI_NDIV_7_0_CH0 0x5 +#define TI_NDIV_7_0_CH0_VAL 0x7D +#define TI_NDIV_15_8_CH0 0x6 +#define TI_PDIV_CH0 0x13 +#define TI_PDIV_CH0_VAL 0x80 +#define TI_NDIV_7_0_CH1 0x45 +#define TI_NDIV_7_0_CH1_VAL 0x7D +#define TI_NDIV_15_8_CH1 0x46 +#define TI_PDIV_CH1 0x53 +#define TI_PDIV_CH1_VAL 0x80 +#define TI_MASH_ORDER_CH0 0x4 +#define TI_MASH_ORDER_CH0_VAL 0x1 +#define TI_NUM_7_0_CH0 0x1E +#define TI_NUM_7_0_CH0_VAL 0x0 +#define TI_NUM_15_8_CH0 0x1F +#define TI_NUM_15_8_CH0_VAL 0x0 +#define TI_NUM_23_16_CH0 0x20 +#define TI_NUM_23_16_CH0_VAL 0x0 +#define TI_MASH_ORDER_CH1 0x44 +#define TI_MASH_ORDER_CH1_VAL 0x1 +#define TI_NUM_7_0_CH1 0x5E +#define TI_NUM_7_0_CH1_VAL 0x0 +#define TI_NUM_15_8_CH1 0x5F +#define TI_NUM_15_8_CH1_VAL 0x0 +#define TI_NUM_23_16_CH1 0x60 +#define TI_NUM_23_16_CH1_VAL 0x0 +#define TI_VCO_CH0 0xE +#define TI_VCO_CH1 0x4E +#define TI_VCO_CH_SEL_VCO_1 0xC3 +#define TI_VCO_CH_SEL_VCO_2 0xC7 +#define TI_VCO_CH_SEL_VCO_3 0xCB +#define TI_VCO_CH_SEL_VCO_4 0xCF +// APB bus registers +#define TI_APB_CTL 0x48 +#define TI_APB_CTL_EN 0x1 +#define TI_APB_ADDR0 0x49 +#define TI_APB_ADDR1 0x4a +#define TI_APB_DATA0 0x4b +#define TI_APB_DATA1 0x4c +#define TI_APB_DATA2 0x4d +#define TI_APB_DATA3 0x4e +#define TI_APB_LINK_ENABLE 0x0 +#define TI_APB_EMPTY_FILL 0x0 +#define TI_APB_LINK_ENABLE_LOW 0x0 +#define TI_APB_LINK_ENABLE_HIGH 0x1 +#define TI_APB_MAX_LINK_RATE 0x74 +#define TI_APB_MAX_LINK_RATE_MASK 0xFF +#define TI_APB_MAX_LINK_RATE_162 0x06 // 1.62 Gbps +#define TI_APB_MAX_LINK_RATE_270 0x0A // 2.7 Gbps +#define TI_APB_MAX_LINK_RATE_540 0x14 // 5.4 Gbps +#define TI_APB_MAX_LINK_RATE_810 0x1E // 8.1 Gbps +#define TI_APB_MAX_LANE_COUNT 0x70 +#define TI_APB_MAX_LANE_COUNT_1 0x1 +#define TI_APB_MAX_LANE_COUNT_2 0x2 +#define TI_APB_MAX_LANE_COUNT_4 0x4 +#define TI_APB_MISC_CONFIG 0x18 +#define TI_APB_MISC_CONFIG_MAX_DOWNSPREAD_CONFIG (1 << 4) +#define TI_APB_MISC_CONFIG_DISABLE_INACTIVE_COUNT (1 << 2) + +#define TI_APB_VIDEO_INPUT_RESET 0x54 +#define TI_APB_VIDEO_INPUT_RESET_TRIGGER 0x1 + +#define TI_LINK_LAYER_CTL 0x0 +#define TI_LINK_LAYER_CTL_EN_NEW_TSLOT1 0x8 +#define TI_LINK_LAYER_CTL_LINK_LAYER_1_EN 0x4 +#define TI_LINK_LAYER_CTL_EN_NEW_TSLOT0 0x2 +#define TI_LINK_LAYER_CTL_LINK_LAYER_0_EN 0x1 + +#define TI_LINK0_STREAM_EN 0x1 +#define TI_LINK1_STREAM_EN 0x11 +#define TI_LINK_STREAM_EN0 0x1 +#define TI_LINK_STREAM_EN1 0x2 +#define TI_LINK_STREAM_EN2 0x4 +#define TI_LINK_STREAM_EN3 0x8 +#define TI_LINK_STREAM_EN4 0x10 +#define TI_LINK0_SLOT_REQ0 0x6 +#define TI_LINK1_SLOT_REQ0 0x16 +#define TI_VP_WIDTH0 0x20 +#define TI_VID_PROC_CFG_VP0 0x1 +#define TI_VID_PROC_CFG_VP0_DEFAULT 0xA8 +#define TI_DP_H_ACTIVE0_VP0 0x2 +#define TI_DP_H_ACTIVE1_VP0 0x3 +#define TI_VID_H_ACTIVE0_VP0 0x10 +#define TI_VID_H_ACTIVE1_VP0 0x11 +#define TI_VID_H_BACK0_VP0 0x12 +#define TI_VID_H_BACK1_VP0 0x13 +#define TI_VID_H_WIDTH0_VP0 0x14 +#define TI_VID_H_WIDTH1_VP0 0x15 +#define TI_VID_H_TOTAL0_VP0 0x16 +#define TI_VID_H_TOTAL1_VP0 0x17 +#define TI_VID_V_ACTIVE0_VP0 0x18 +#define TI_VID_V_ACTIVE1_VP0 0x19 +#define TI_VID_V_BACK0_VP0 0x1A +#define TI_VID_V_BACK1_VP0 0x1B +#define TI_VID_V_WIDTH0_VP0 0x1C +#define TI_VID_V_WIDTH1_VP0 0x1D +#define TI_VID_V_FRONT0_VP0 0x1F +#define TI_VID_V_FRONT1_VP0 0x1E +#define TI_VID_PROC_CFG2_VP0 0x27 +#define TI_PCLK_GEN_M_0_VP0 0x23 +#define TI_PCLK_GEN_M_1_VP0 0x24 +#define TI_PCLK_GEN_N_VP0 0x25 + +#define TI_VP_CONFIG_REG 0x43 +#define TI_VP_CONFIG_REG_VID_STREAM_1 0x0 +#define TI_VP_CONFIG_REG_VID_STREAM_2 0x1 +#define TI_VP_CONFIG_REG_VID_STREAM_3 0x2 +#define TI_VP_CONFIG_REG_VID_STREAM_4 0x3 +#define TI_VP_ENABLE_REG 0x44 +#define TI_VP_ENABLE_REG_VP0_ENABLE 0x1 +#define TI_VP_ENABLE_REG_VP1_ENABLE 0x2 +#define TI_VP_ENABLE_REG_VP2_ENABLE 0x4 +#define TI_VP_ENABLE_REG_VP3_ENABLE 0x8 + +#define TI_HDCP_TX_ID0 0xF0 +#define TI_HDCP_TX_ID0_VAL 0x5F +#define TI_HDCP_TX_ID1 0xF1 +#define TI_HDCP_TX_ID1_VAL 0x55 +#define TI_HDCP_TX_ID2 0xF2 +#define TI_HDCP_TX_ID2_VAL_1 0x42 +#define TI_HDCP_TX_ID2_VAL_2 0x48 +#define TI_HDCP_TX_ID3 0xF3 +#define TI_HDCP_TX_ID3_VAL 0x39 +#define TI_HDCP_TX_ID4 0xF4 +#define TI_HDCP_TX_ID4_VAL 0x38 +#define TI_HDCP_TX_ID5 0xF5 +#define TI_HDCP_TX_ID5_VAL 0x33 +#define TI_HDCP_TX_ID6 0xF6 +#define TI_HDCP_TX_ID6_VAL 0x30 + +// Custom Macros +#define TI_FPD_LINK_COUNT 0x2 + +struct ti_fpdlink_dp_ser_source { + struct fwnode_handle *fwnode; +}; + +static const struct regmap_config ti_fpdlink_dp_ser_i2c_regmap = { + .reg_bits = 8, + .val_bits = 8, +}; + +struct view_port { + u16 h_active; + u16 h_back_porch; + u16 h_width; + u16 h_total; + u16 v_active; + u16 v_back_porch; + u16 v_width; + u16 v_front_porch; +}; + +struct ti_fpdlink_dp_ser_priv { + struct i2c_client *client; + struct gpio_desc *gpiod_pwrdn; + struct regmap *regmap; + u8 dprx_lane_count; + u8 dprx_link_rate; + struct view_port vp; + bool link_0_select; + bool link_1_select; +}; + +static int ti_i2c_read(struct ti_fpdlink_dp_ser_priv *priv, u8 reg) +{ + int ret = 0; + int val = 0; + + ret = regmap_read(priv->regmap, reg, &val); + if (ret < 0) + dev_err(&priv->client->dev, + "%s: register 0x%02x read failed (%d)\n", + __func__, reg, ret); + + return val; +} + +static int ti_i2c_write(struct ti_fpdlink_dp_ser_priv *priv, u8 reg, int val) +{ + int ret = 0; + + ret = regmap_write(priv->regmap, reg, val); + if (ret < 0) + dev_err(&priv->client->dev, + "%s: register 0x%02x write failed (%d)\n", + __func__, reg, ret); + + return ret; +} + +static int ti_page_indirect_i2c_write(struct ti_fpdlink_dp_ser_priv *priv, + u32 page, u32 buffer[][2], u32 length) +{ + int i, ret = 0; + + // set page at i2c indirect register 0x40 + ret = ti_i2c_write(priv, TI_IND_ACC_CTL, page); + if (ret < 0) + goto fail; + + for (i = 0; i < length; i++) { + // write address at i2c indirect register 0x41 + ret = ti_i2c_write(priv, TI_IND_ACC_ADDR, buffer[i][0]); + if (ret < 0) + goto fail; + + // write data at i2c indirect register 0x42 + ret = ti_i2c_write(priv, TI_IND_ACC_DATA, buffer[i][1]); + if (ret < 0) + goto fail; + } + +fail: + if (ret < 0) + dev_err(&priv->client->dev, "%s: failed (%d)\n", __func__, ret); + + return ret; +} + +static void ti_fpdlink_dp_apb_write(struct ti_fpdlink_dp_ser_priv *priv, + u8 apb_addr[2], u8 apb_data[4]) +{ + // addr 0 + ti_i2c_write(priv, TI_APB_ADDR0, apb_addr[0]); + + // addr 1 + ti_i2c_write(priv, TI_APB_ADDR1, apb_addr[1]); + + // data 0 + ti_i2c_write(priv, TI_APB_DATA0, apb_data[0]); + + // data 1 + ti_i2c_write(priv, TI_APB_DATA1, apb_data[1]); + + // data 2 + ti_i2c_write(priv, TI_APB_DATA2, apb_data[2]); + + // data 3 + ti_i2c_write(priv, TI_APB_DATA3, apb_data[3]); +} + +static void ti_program_pll_port_0(struct ti_fpdlink_dp_ser_priv *priv) +{ + u32 page = 0; + u32 buf[20][2]; + + ti_i2c_write(priv, TI_TX_PORT_SEL, TI_TX_PORT_SEL_VAL_PORT_0); + + // Set FPD4 on port 0 + { + page = TI_IND_ACC_PAGE_9 << TI_IND_ACC_CTL_SEL_SHIFT; + // Register offset not present in datasheet but present in reference script + buf[0][0] = 0x84; + buf[0][1] = 0x2; + ti_page_indirect_i2c_write(priv, page, buf, 1); + } + + // Program PLL page - port0 + { + page = TI_IND_ACC_PAGE_2 << TI_IND_ACC_CTL_SEL_SHIFT; + buf[0][0] = TI_NDIV_7_0_CH0; + buf[0][1] = TI_NDIV_7_0_CH0_VAL; + buf[1][0] = TI_PDIV_CH0; + buf[1][1] = TI_PDIV_CH0_VAL; + ti_page_indirect_i2c_write(priv, page, buf, 2); + } + + // Select port 0 and set sampling rate + { + ti_i2c_write(priv, TI_TX_PORT_SEL, TI_TX_PORT_SEL_VAL_PORT_0); + // Register offset not present in datasheet but present in reference script + ti_i2c_write(priv, 0x6a, 0x4a); + ti_i2c_write(priv, 0x6e, 0x80); + } + + // Set halfrate mode + { + ti_i2c_write(priv, TI_GENERAL_CFG2, TI_GENERAL_CFG2_VAL); + } + + // PLLs + { + page = TI_IND_ACC_PAGE_2 << TI_IND_ACC_CTL_SEL_SHIFT; + // Zero Out PLL + { + buf[0][0] = TI_MASH_ORDER_CH0; + buf[0][1] = TI_MASH_ORDER_CH0_VAL; + buf[1][0] = TI_NUM_7_0_CH0; + buf[1][1] = TI_NUM_7_0_CH0_VAL; + buf[2][0] = TI_NUM_15_8_CH0; + buf[2][1] = TI_NUM_15_8_CH0_VAL; + buf[3][0] = TI_NUM_23_16_CH0; + buf[3][1] = TI_NUM_23_16_CH0_VAL; + ti_page_indirect_i2c_write(priv, page, buf, 4); + } + // Configure VCO + { + buf[0][0] = TI_VCO_CH0; + buf[0][1] = TI_VCO_CH_SEL_VCO_2; + ti_page_indirect_i2c_write(priv, page, buf, 1); + // soft reset PLL + ti_i2c_write(priv, TI_RESET_CTL, TI_RESET_CTL_VAL_SOFT_RESET_PLL); + } + // Enable PLL + { + // Below registers not given in data sheet. + buf[0][0] = 0x1b; + buf[0][1] = 0x0; + ti_page_indirect_i2c_write(priv, page, buf, 1); + } + } + +} + +static void ti_program_pll_port_1(struct ti_fpdlink_dp_ser_priv *priv) +{ + u32 page = 0; + u32 buf[20][2]; + + ti_i2c_write(priv, TI_TX_PORT_SEL, TI_TX_PORT_SEL_VAL_PORT_1); + + // Set FPD4 on port 1 + { + page = TI_IND_ACC_PAGE_9 << TI_IND_ACC_CTL_SEL_SHIFT; + // Register offset not present in datasheet but present in reference script + buf[0][0] = 0x94; + buf[0][1] = 0x2; + ti_page_indirect_i2c_write(priv, page, buf, 1); + } + + // Program PLL page - port1 + { + page = TI_IND_ACC_PAGE_2 << TI_IND_ACC_CTL_SEL_SHIFT; + buf[0][0] = TI_NDIV_7_0_CH1; + buf[0][1] = TI_NDIV_7_0_CH1_VAL; + buf[1][0] = TI_PDIV_CH1; + buf[1][1] = TI_PDIV_CH1_VAL; + ti_page_indirect_i2c_write(priv, page, buf, 2); + } + + // Select port 1 and set sampling rate + { + ti_i2c_write(priv, TI_TX_PORT_SEL, TI_TX_PORT_SEL_VAL_PORT_1); + // Register offset not present in datasheet but present in reference script + ti_i2c_write(priv, 0x6a, 0x4a); + ti_i2c_write(priv, 0x6e, 0x80); + } + + // Set halfrate mode + { + ti_i2c_write(priv, TI_GENERAL_CFG2, TI_GENERAL_CFG2_VAL); + } + + { + page = TI_IND_ACC_PAGE_2 << TI_IND_ACC_CTL_SEL_SHIFT; + // Zero Out PLL + { + buf[0][0] = TI_MASH_ORDER_CH1; + buf[0][1] = TI_MASH_ORDER_CH1_VAL; + buf[1][0] = TI_NUM_7_0_CH1; + buf[1][1] = TI_NUM_7_0_CH1_VAL; + buf[2][0] = TI_NUM_15_8_CH1; + buf[2][1] = TI_NUM_15_8_CH1_VAL; + buf[3][0] = TI_NUM_23_16_CH1; + buf[3][1] = TI_NUM_23_16_CH1_VAL; + ti_page_indirect_i2c_write(priv, page, buf, 4); + } + // Configure VCO + { + buf[0][0] = TI_VCO_CH1; + buf[0][1] = TI_VCO_CH_SEL_VCO_2; + ti_page_indirect_i2c_write(priv, page, buf, 1); + // soft reset PLL + ti_i2c_write(priv, TI_RESET_CTL, TI_RESET_CTL_VAL_SOFT_RESET_PLL); + } + // Enable PLL + { + // Below registers not given in data sheet. + buf[0][0] = 0x5b; + buf[0][1] = 0x0; + ti_page_indirect_i2c_write(priv, page, buf, 2); + } + } + +} +/* Set FPD4 and program PLL for both ports */ +static void ti_program_pll(struct ti_fpdlink_dp_ser_priv *priv) +{ + u32 page = 0; + u32 buf[20][2]; + + // Disable PLL - PLL0 and PLL1 + { + page = TI_IND_ACC_PAGE_2 << TI_IND_ACC_CTL_SEL_SHIFT; + buf[0][0] = TI_PLL_0; + buf[0][1] = TI_PLL_DISABLE_VAL << TI_PLL_DISABLE_SHIFT; + buf[1][0] = TI_PLL_1; + buf[1][1] = TI_PLL_DISABLE_VAL << TI_PLL_DISABLE_SHIFT; + ti_page_indirect_i2c_write(priv, page, buf, 2); + } + + // DISABLE FPD3 FIFO pass through, Enable FPD4 independent mode. + { + // These are default values from data sheet. + ti_i2c_write(priv, TI_FPD3_FIFO_CFG, TI_FPD3_FIFO_CFG_VAL); + ti_i2c_write(priv, TI_FPD4_CFG, TI_FPD4_CFG_FPD4_TX_MODE_IND); + ti_i2c_write(priv, TI_GENERAL_CFG2, TI_GENERAL_CFG2_VAL); + } + + if (priv->link_0_select) + ti_program_pll_port_0(priv); + + if (priv->link_1_select) + ti_program_pll_port_1(priv); + + // soft reset Ser + ti_i2c_write(priv, TI_RESET_CTL, TI_RESET_CTL_VAL_SOFT_RESET_SER); + + // Enable I2C Passthrough + { + int pass_thr_val = ti_i2c_read(priv, TI_GENERAL_CFG); + int pass_thr_reg = pass_thr_val | TI_GENERAL_CFG_I2C_PASS_THROUGH_MASK; + + ti_i2c_write(priv, 0x7, pass_thr_reg); + } +} + +static void ti_program_dp_config(struct ti_fpdlink_dp_ser_priv *priv) +{ + u32 page = 0; + u32 buf[20][2]; + u8 apb_addr[2]; + u8 apb_data[4]; + + // Select ser link layer + if (priv->link_0_select) { + page = TI_IND_ACC_PAGE_11 << TI_IND_ACC_CTL_SEL_SHIFT; + buf[0][0] = TI_LINK_LAYER_CTL; + buf[0][1] = TI_LINK_LAYER_CTL_EN_NEW_TSLOT0 | TI_LINK_LAYER_CTL_LINK_LAYER_0_EN; + ti_page_indirect_i2c_write(priv, page, buf, 1); + // select write to port 0 + ti_i2c_write(priv, TI_TX_PORT_SEL, TI_TX_PORT_SEL_VAL_PORT_0); + } + + if (priv->link_1_select) { + page = TI_IND_ACC_PAGE_11 << TI_IND_ACC_CTL_SEL_SHIFT; + buf[0][0] = TI_LINK_LAYER_CTL; + buf[0][1] = TI_LINK_LAYER_CTL_EN_NEW_TSLOT0 | TI_LINK_LAYER_CTL_LINK_LAYER_1_EN; + ti_page_indirect_i2c_write(priv, page, buf, 1); + // select write to port 1 + ti_i2c_write(priv, TI_TX_PORT_SEL, TI_TX_PORT_SEL_VAL_PORT_1); + } + + // Set DP config + { + // Enable APB interface + ti_i2c_write(priv, TI_APB_CTL, TI_APB_CTL_EN); + + // Force HPD low + { + memset(apb_addr, 0, 2); + memset(apb_data, 0, 4); + apb_addr[0] = TI_APB_LINK_ENABLE; + apb_data[0] = TI_APB_LINK_ENABLE_LOW; + ti_fpdlink_dp_apb_write(priv, apb_addr, apb_data); + } + + // Set max advertised link rate. + { + memset(apb_addr, 0, 2); + memset(apb_data, 0, 4); + apb_addr[0] = TI_APB_MAX_LINK_RATE; + apb_data[0] = priv->dprx_link_rate; + ti_fpdlink_dp_apb_write(priv, apb_addr, apb_data); + } + + // Set max advertised lane count. + { + memset(apb_addr, 0, 2); + memset(apb_data, 0, 4); + apb_addr[0] = TI_APB_MAX_LANE_COUNT; + apb_data[0] = priv->dprx_lane_count; + ti_fpdlink_dp_apb_write(priv, apb_addr, apb_data); + } + + // Request min VOD swing of 0x02 + { + memset(apb_addr, 0, 2); + memset(apb_data, 0, 4); + // below register not given in data sheet + apb_addr[0] = 0x14; + apb_addr[1] = 2; + apb_data[0] = 2; + ti_fpdlink_dp_apb_write(priv, apb_addr, apb_data); + } + + // Set SST/MST mode and DP/eDP Mode. Add support for MST in future. + { + // This is set to default value + memset(apb_addr, 0, 2); + memset(apb_data, 0, 4); + apb_addr[0] = TI_APB_MISC_CONFIG; + apb_data[0] = TI_APB_MISC_CONFIG_MAX_DOWNSPREAD_CONFIG + | TI_APB_MISC_CONFIG_DISABLE_INACTIVE_COUNT; + ti_fpdlink_dp_apb_write(priv, apb_addr, apb_data); + } + + // Disable line reset for VS0 + { + // below register not given in data sheet + memset(apb_addr, 0, 2); + memset(apb_data, 0, 4); + apb_addr[0] = 0xc; + apb_addr[1] = 0xa; + apb_data[0] = 1; + ti_fpdlink_dp_apb_write(priv, apb_addr, apb_data); + } + + // Force HPD high to trigger link training + { + memset(apb_addr, 0, 2); + memset(apb_data, 0, 4); + apb_addr[0] = TI_APB_LINK_ENABLE; + apb_data[0] = TI_APB_LINK_ENABLE_HIGH; + ti_fpdlink_dp_apb_write(priv, apb_addr, apb_data); + } + } +} + +static void ti_program_viewport_timing(struct ti_fpdlink_dp_ser_priv *priv) +{ + u32 page = 0; + u32 buf[20][2]; + u8 apb_addr[2]; + u8 apb_data[4]; + + // Program VP config + { + //Set VP_SRC_SELECT to Stream 0 for SST Mode and program timings + page = TI_IND_ACC_PAGE_12 << TI_IND_ACC_CTL_SEL_SHIFT; + page = page | TI_IND_ACC_CTL_AUTO_INC_MASK; + // set page address with autoincrement writes. + // ie. If first register is set to 0x2 (using 0x41) then first write will be to 0x2, + // next write will 0x3 and so on. + ti_i2c_write(priv, TI_IND_ACC_CTL, page); + ti_i2c_write(priv, TI_IND_ACC_ADDR, TI_VID_PROC_CFG_VP0); + ti_i2c_write(priv, TI_IND_ACC_DATA, TI_VID_PROC_CFG_VP0_DEFAULT); + // VID H Active + ti_i2c_write(priv, TI_IND_ACC_ADDR, TI_DP_H_ACTIVE0_VP0); + ti_i2c_write(priv, TI_IND_ACC_DATA, priv->vp.h_active & 0xFF); + ti_i2c_write(priv, TI_IND_ACC_DATA, (priv->vp.h_active & 0xFF00) >> 8); + //Horizontal Active - VID_H_ACTIVE0_VP0 + ti_i2c_write(priv, TI_IND_ACC_ADDR, TI_VID_H_ACTIVE0_VP0); + ti_i2c_write(priv, TI_IND_ACC_DATA, priv->vp.h_active & 0xFF); + ti_i2c_write(priv, TI_IND_ACC_DATA, (priv->vp.h_active & 0xFF00) >> 8); + //Horizontal Back Porch - VID_H_BACK0_VP0 + ti_i2c_write(priv, TI_IND_ACC_DATA, priv->vp.h_back_porch & 0xFF); + ti_i2c_write(priv, TI_IND_ACC_DATA, (priv->vp.h_back_porch & 0xFF00) >> 8); + //Horizontal Sync - VID_H_WIDTH0_VP0 + ti_i2c_write(priv, TI_IND_ACC_DATA, priv->vp.h_width & 0xFF); + ti_i2c_write(priv, TI_IND_ACC_DATA, (priv->vp.h_width & 0xFF00) >> 8); + //Horizontal Total - VID_H_TOTAL0_VP0 + ti_i2c_write(priv, TI_IND_ACC_DATA, priv->vp.h_total & 0xFF); + ti_i2c_write(priv, TI_IND_ACC_DATA, (priv->vp.h_total & 0xFF00) >> 8); + //Vertical Active - VID_V_ACTIVE0_VP0 + ti_i2c_write(priv, TI_IND_ACC_DATA, priv->vp.v_active & 0xFF); + ti_i2c_write(priv, TI_IND_ACC_DATA, (priv->vp.v_active & 0xFF00) >> 8); + //Vertical Back Porch - VID_V_BACK0_VP0 + ti_i2c_write(priv, TI_IND_ACC_DATA, priv->vp.v_back_porch & 0xFF); + ti_i2c_write(priv, TI_IND_ACC_DATA, (priv->vp.v_back_porch & 0xFF00) >> 8); + //Vertical Sync - VID_V_WIDTH0_VP0 + ti_i2c_write(priv, TI_IND_ACC_DATA, priv->vp.v_width & 0xFF); + ti_i2c_write(priv, TI_IND_ACC_DATA, (priv->vp.v_width & 0xFF00) >> 8); + //Vertical Front Porch - VID_V_FRONT0_VP0 + ti_i2c_write(priv, TI_IND_ACC_DATA, priv->vp.v_front_porch & 0xFF); + ti_i2c_write(priv, TI_IND_ACC_DATA, (priv->vp.v_front_porch & 0xFF00) >> 8); + //HSYNC Polarity = +, VSYNC Polarity = +, - VID_PROC_CFG2_VP0 + ti_i2c_write(priv, TI_IND_ACC_ADDR, TI_VID_PROC_CFG2_VP0); + ti_i2c_write(priv, TI_IND_ACC_DATA, 0x0); + //M/N Register - M,M and N value + ti_i2c_write(priv, TI_IND_ACC_ADDR, TI_PCLK_GEN_M_0_VP0); + ti_i2c_write(priv, TI_IND_ACC_DATA, 0x14); + ti_i2c_write(priv, TI_IND_ACC_DATA, 0xe); + ti_i2c_write(priv, TI_IND_ACC_DATA, 0xf); + } + + // Enable VP + { + //Set number of VPs used = 1 + ti_i2c_write(priv, TI_VP_CONFIG_REG, TI_VP_CONFIG_REG_VID_STREAM_1); + //Enable video processors + ti_i2c_write(priv, TI_VP_ENABLE_REG, TI_VP_ENABLE_REG_VP0_ENABLE); + } + + // Video Input Reset + { + memset(apb_addr, 0, 2); + memset(apb_data, 0, 4); + apb_addr[0] = TI_APB_VIDEO_INPUT_RESET; + apb_data[0] = TI_APB_VIDEO_INPUT_RESET_TRIGGER; + ti_fpdlink_dp_apb_write(priv, apb_addr, apb_data); + } + + { + // Set color depth to 24 bpp for VP0 + page = TI_IND_ACC_PAGE_12 << TI_IND_ACC_CTL_SEL_SHIFT; + buf[0][0] = TI_FPD4_PGCFG_VP0; + buf[0][1] = TI_FPD4_PGCFG_DEFAULT; + ti_page_indirect_i2c_write(priv, page, buf, 1); + // Soft Reset PLL + ti_i2c_write(priv, TI_RESET_CTL, TI_RESET_CTL_VAL_SOFT_RESET_PLL); + } + + // Configure TX Link Layer 0 - stream 0 enable, time slot 0, vp bpp, enable + if (priv->link_0_select) { + page = TI_IND_ACC_PAGE_11 << TI_IND_ACC_CTL_SEL_SHIFT; + buf[0][0] = TI_LINK0_STREAM_EN; + buf[0][1] = TI_LINK_STREAM_EN0; + buf[1][0] = TI_LINK0_SLOT_REQ0; + buf[1][1] = 0x3c; // fixed value, find out why? + buf[2][0] = TI_VP_WIDTH0; + buf[2][1] = 0x55; // default set to 24 bit. Need to fetch from DTS? + buf[3][0] = TI_LINK_LAYER_CTL; + buf[3][1] = TI_LINK_LAYER_CTL_EN_NEW_TSLOT0 | TI_LINK_LAYER_CTL_LINK_LAYER_0_EN; + ti_page_indirect_i2c_write(priv, page, buf, 4); + } + + if (priv->link_1_select) { + page = TI_IND_ACC_PAGE_11 << TI_IND_ACC_CTL_SEL_SHIFT; + buf[0][0] = TI_LINK1_STREAM_EN; + buf[0][1] = TI_LINK_STREAM_EN0; + buf[1][0] = TI_LINK1_SLOT_REQ0; + buf[1][1] = 0x3c; // fixed value, find out why? + buf[2][0] = TI_VP_WIDTH0; + buf[2][1] = 0x55; // default set to 24 bit. Need to fetch from DTS? + buf[3][0] = TI_LINK_LAYER_CTL; + buf[3][1] = TI_LINK_LAYER_CTL_EN_NEW_TSLOT0 | TI_LINK_LAYER_CTL_LINK_LAYER_1_EN; + ti_page_indirect_i2c_write(priv, page, buf, 4); + } +} + +static int ti_fpdlink_dp_ser_init(struct device *dev) +{ + struct ti_fpdlink_dp_ser_priv *priv; + struct i2c_client *client; + int ret = 0; + + client = to_i2c_client(dev); + priv = i2c_get_clientdata(client); + + priv->gpiod_pwrdn = devm_gpiod_get_optional(&client->dev, "enable", + GPIOD_OUT_HIGH); + if (IS_ERR(priv->gpiod_pwrdn)) { + dev_err(dev, "%s: gpiopwrdn is not enabled\n", __func__); + return PTR_ERR(priv->gpiod_pwrdn); + } + gpiod_set_consumer_name(priv->gpiod_pwrdn, "ti_fpdlink_dp_ser-pwrdn"); + + /* Drive PWRDNB pin high to power up the serializer */ + gpiod_set_value_cansleep(priv->gpiod_pwrdn, 1); + + /* Wait ~20ms for link to establish after power up */ + usleep_range(20000, 21000); + + // Init addresses + ti_i2c_write(priv, TI_TARGET_ID_0, TI_TARGET_ID_VAL); + ti_i2c_write(priv, TI_TARGET_ALIAS_ID_0, TI_TARGET_ALIAS_ID_VAL); + ti_i2c_write(priv, TI_TARGET_DEST_0, TI_TARGET_DEST_VAL); + + ti_program_pll(priv); + + ti_program_dp_config(priv); + + ti_program_viewport_timing(priv); + + return ret; +} + +static int ti_fpdlink_dp_ser_parse_dt_link_config(struct i2c_client *client, + struct ti_fpdlink_dp_ser_priv *priv) +{ + struct device *dev = &priv->client->dev; + struct device_node *ser = dev->of_node; + int err = 0; + u32 fpd_link_select[TI_FPD_LINK_COUNT] = { 0 }; + + err = of_property_read_variable_u32_array(ser, "fpd-link-select", + fpd_link_select, 1, + ARRAY_SIZE(fpd_link_select)); + if (err < 0) { + dev_err(dev, + "%s: fpd-link-select property not found or invalid\n", + __func__); + return -EINVAL; + } + + if (fpd_link_select[0] == 1) + priv->link_0_select = true; + + if (fpd_link_select[1] == 1) + priv->link_1_select = true; + + if (priv->link_0_select && priv->link_1_select) { + dev_err(dev, + "%s: fpd-link-select: both link cannot be selected\n", + __func__); + return -EINVAL; + } + + if (!(priv->link_0_select || priv->link_1_select)) { + dev_err(dev, + "%s: fpd-link-select: No link is selected, enabling link 0\n", + __func__); + priv->link_0_select = true; + } + + dev_info(dev, + "%s: fpd-link-select link_0_select = %d, link_1_select = %d\n", + __func__, priv->link_0_select, priv->link_1_select); + + return err; +} + +static int ti_fpdlink_dp_ser_parse_dt_dp_config(struct i2c_client *client, + struct ti_fpdlink_dp_ser_priv *priv) +{ + struct device *dev = &priv->client->dev; + struct device_node *ser = dev->of_node; + int err = 0; + u32 val = 0; + + dev_info(dev, "%s: parsing serializer device tree:\n", __func__); + + err = of_property_read_u32(ser, "dprx-lane-count", &val); + if (err) { + if (err == -EINVAL) { + dev_err(dev, "%s: - dprx-lane-count property not found\n", + __func__); + /* default value: 4 */ + priv->dprx_lane_count = 4; + dev_err(dev, "%s: dprx-lane-count set to default val: 4\n", + __func__); + } else { + return err; + } + } else { + /* set dprx-lane-count */ + if ((val == TI_APB_MAX_LANE_COUNT_1) || + (val == TI_APB_MAX_LANE_COUNT_2) || + (val == TI_APB_MAX_LANE_COUNT_4)) { + priv->dprx_lane_count = val; + } else { + dev_err(dev, "%s: - invalid lane-count %i from DTS\n", + __func__, val); + return err; + } + } + + err = of_property_read_u32(ser, "dprx-link-rate", &val); + if (err) { + if (err == -EINVAL) { + dev_err(dev, "%s: - dprx-link-rate property not found\n", + __func__); + /* default value: 0x14 which is 5.4 Gbps */ + priv->dprx_link_rate = TI_APB_MAX_LINK_RATE_540; + dev_err(dev, "%s: dprx-link-rate set to default val: 0x14\n", + __func__); + } else { + return err; + } + } else { + /* set dprx-link-rate*/ + if ((val == TI_APB_MAX_LINK_RATE_162) || + (val == TI_APB_MAX_LINK_RATE_270) || + (val == TI_APB_MAX_LINK_RATE_540) || + (val == TI_APB_MAX_LINK_RATE_810)) { + priv->dprx_link_rate = val; + dev_info(dev, "%s: - dprx-link-rate %i\n", __func__, val); + } else { + dev_err(dev, "%s: - invalid link-count %i from DTS\n", + __func__, val); + return err; + } + } + + return 0; +} + +static int ti_fpdlink_dp_ser_parse_dt_viewport_config(struct i2c_client *client, + struct ti_fpdlink_dp_ser_priv *priv) +{ + struct device *dev = &priv->client->dev; + struct device_node *ser = dev->of_node; + int err = 0; + u32 val = 0; + struct device_node *timings_handle; + + err = of_property_read_u32(ser, "timings-phandle", &val); + if (err) { + dev_err(dev, "%s: - timings-phandle property not found\n", + __func__); + return err; + } + + timings_handle = of_find_node_by_phandle(val); + if (timings_handle == NULL) { + dev_err(dev, "%s: - can not get timings node\n", + __func__); + return -1; + } + + err = of_property_read_u32(timings_handle, "hactive", &val); + if (err) { + dev_err(dev, "%s: - hactive property not found\n", + __func__); + return err; + } + priv->vp.h_active = val; + dev_info(dev, "%s: - hactive - %i\n", __func__, val); + + err = of_property_read_u32(timings_handle, "hback-porch", &val); + if (err) { + dev_err(dev, "%s: - hback-porch property not found\n", + __func__); + return err; + } + priv->vp.h_back_porch = val; + dev_info(dev, "%s: - hback-porch - %i\n", __func__, val); + + err = of_property_read_u32(timings_handle, "hsync-len", &val); + if (err) { + dev_err(dev, "%s: - hsync-len property not found\n", + __func__); + return err; + } + priv->vp.h_width = val; + dev_info(dev, "%s: - hsync-len - %i\n", __func__, val); + + err = of_property_read_u32(timings_handle, "hfront-porch", &val); + if (err) { + dev_err(dev, "%s: - hfront-porch property not found\n", + __func__); + return err; + } + priv->vp.h_total = val + priv->vp.h_width + priv->vp.h_back_porch + priv->vp.h_active; + dev_info(dev, "%s: - htotal - %i\n", __func__, priv->vp.h_total); + + err = of_property_read_u32(timings_handle, "vactive", &val); + if (err) { + dev_err(dev, "%s: - vactive property not found\n", + __func__); + return err; + } + priv->vp.v_active = val; + dev_info(dev, "%s: - vactive - %i\n", __func__, val); + + err = of_property_read_u32(timings_handle, "vback-porch", &val); + if (err) { + dev_err(dev, "%s: - vback-porch property not found\n", + __func__); + return err; + } + priv->vp.v_back_porch = val; + dev_info(dev, "%s: - vback-porch - %i\n", __func__, val); + + err = of_property_read_u32(timings_handle, "vsync-len", &val); + if (err) { + dev_err(dev, "%s: - vsync-len property not found\n", + __func__); + return err; + } + priv->vp.v_width = val; + dev_info(dev, "%s: - v-width - %i\n", __func__, val); + + err = of_property_read_u32(timings_handle, "vfront-porch", &val); + if (err) { + dev_err(dev, "%s: - v-front-porch property not found\n", + __func__); + return err; + } + priv->vp.v_front_porch = val; + dev_info(dev, "%s: - v-front-porch - %i\n", __func__, val); + + return 0; +} + +static int ti_fpdlink_dp_ser_probe(struct i2c_client *client) +{ + struct ti_fpdlink_dp_ser_priv *priv; + struct device *dev; + int ret = 0; + + priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL); + if (priv == NULL) + return -ENOMEM; + + priv->client = client; + i2c_set_clientdata(client, priv); + + priv->regmap = devm_regmap_init_i2c(client, &ti_fpdlink_dp_ser_i2c_regmap); + if (IS_ERR(priv->regmap)) + return PTR_ERR(priv->regmap); + + dev = &priv->client->dev; + + ret = ti_fpdlink_dp_ser_parse_dt_link_config(client, priv); + if (ret < 0) { + dev_err(dev, "%s: error parsing device tree for link config\n", __func__); + return -EFAULT; + } + + ret = ti_fpdlink_dp_ser_parse_dt_dp_config(client, priv); + if (ret < 0) { + dev_err(dev, "%s: error parsing device tree for dp config\n", __func__); + return -EFAULT; + } + + ret = ti_fpdlink_dp_ser_parse_dt_viewport_config(client, priv); + if (ret < 0) { + dev_err(dev, "%s: error parsing device tree for vp config\n", __func__); + return -EFAULT; + } + + dev_err(dev, "%s: TI Serializer initialization started\n", __func__); + ret = ti_fpdlink_dp_ser_init(&client->dev); + if (ret < 0) { + dev_err(dev, "%s: TI Serializer init failed\n", __func__); + return -EFAULT; + } + dev_err(dev, "%s: TI Serializer initialization completed\n", __func__); + + return ret; +} + +#if KERNEL_VERSION(6, 1, 0) <= LINUX_VERSION_CODE +static void ti_fpdlink_dp_ser_remove(struct i2c_client *client) +#else +static int ti_fpdlink_dp_ser_remove(struct i2c_client *client) +#endif +{ + struct ti_fpdlink_dp_ser_priv *priv = i2c_get_clientdata(client); + + i2c_unregister_device(client); + gpiod_set_value_cansleep(priv->gpiod_pwrdn, 0); + +#if KERNEL_VERSION(6, 1, 0) > LINUX_VERSION_CODE + return 0; +#endif +} + +static const struct of_device_id ti_fpdlink_dp_ser_dt_ids[] = { + { .compatible = "ti,ti_fpdlink_dp_ser" }, + {}, +}; +MODULE_DEVICE_TABLE(of, ti_fpdlink_dp_ser_dt_ids); + +static struct i2c_driver ti_fpdlink_dp_ser_i2c_driver = { + .driver = { + .name = "ti_fpdlink_dp_ser", + .of_match_table = of_match_ptr(ti_fpdlink_dp_ser_dt_ids), + }, + .probe_new = ti_fpdlink_dp_ser_probe, + .remove = ti_fpdlink_dp_ser_remove, +}; + +module_i2c_driver(ti_fpdlink_dp_ser_i2c_driver); + +MODULE_DESCRIPTION("TI DP FPDLINK Serializer Driver"); +MODULE_AUTHOR("NVIDIA CORP"); +MODULE_LICENSE("GPL");