| /* |
| * Copyright(c) 2016, Analogix Semiconductor. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 and |
| * only version 2 as published by the Free Software Foundation. |
| * |
| * 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. |
| * |
| * Based on anx7808 driver obtained from chromeos with copyright: |
| * Copyright(c) 2013, Google Inc. |
| * |
| */ |
| #include <linux/delay.h> |
| #include <linux/err.h> |
| #include <linux/interrupt.h> |
| #include <linux/i2c.h> |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/of_gpio.h> |
| #include <linux/of_irq.h> |
| #include <linux/of_platform.h> |
| #include <linux/regmap.h> |
| #include <linux/types.h> |
| #include <linux/gpio/consumer.h> |
| #include <linux/regulator/consumer.h> |
| |
| #include <drm/drmP.h> |
| #include <drm/drm_atomic_helper.h> |
| #include <drm/drm_crtc.h> |
| #include <drm/drm_crtc_helper.h> |
| #include <drm/drm_dp_helper.h> |
| #include <drm/drm_edid.h> |
| |
| #include "analogix-anx78xx.h" |
| |
| #define I2C_NUM_ADDRESSES 5 |
| #define I2C_IDX_TX_P0 0 |
| #define I2C_IDX_TX_P1 1 |
| #define I2C_IDX_TX_P2 2 |
| #define I2C_IDX_RX_P0 3 |
| #define I2C_IDX_RX_P1 4 |
| |
| #define XTAL_CLK 270 /* 27M */ |
| #define AUX_CH_BUFFER_SIZE 16 |
| #define AUX_WAIT_TIMEOUT_MS 15 |
| |
| static const u8 anx78xx_i2c_addresses[] = { |
| [I2C_IDX_TX_P0] = TX_P0, |
| [I2C_IDX_TX_P1] = TX_P1, |
| [I2C_IDX_TX_P2] = TX_P2, |
| [I2C_IDX_RX_P0] = RX_P0, |
| [I2C_IDX_RX_P1] = RX_P1, |
| }; |
| |
| struct anx78xx_platform_data { |
| struct regulator *dvdd10; |
| struct gpio_desc *gpiod_hpd; |
| struct gpio_desc *gpiod_pd; |
| struct gpio_desc *gpiod_reset; |
| |
| int hpd_irq; |
| int intp_irq; |
| }; |
| |
| struct anx78xx { |
| struct drm_dp_aux aux; |
| struct drm_bridge bridge; |
| struct i2c_client *client; |
| struct edid *edid; |
| struct drm_connector connector; |
| struct drm_dp_link link; |
| struct anx78xx_platform_data pdata; |
| struct mutex lock; |
| |
| /* |
| * I2C Slave addresses of ANX7814 are mapped as TX_P0, TX_P1, TX_P2, |
| * RX_P0 and RX_P1. |
| */ |
| struct i2c_client *i2c_dummy[I2C_NUM_ADDRESSES]; |
| struct regmap *map[I2C_NUM_ADDRESSES]; |
| |
| u16 chipid; |
| u8 dpcd[DP_RECEIVER_CAP_SIZE]; |
| |
| bool powered; |
| }; |
| |
| static inline struct anx78xx *connector_to_anx78xx(struct drm_connector *c) |
| { |
| return container_of(c, struct anx78xx, connector); |
| } |
| |
| static inline struct anx78xx *bridge_to_anx78xx(struct drm_bridge *bridge) |
| { |
| return container_of(bridge, struct anx78xx, bridge); |
| } |
| |
| static int anx78xx_set_bits(struct regmap *map, u8 reg, u8 mask) |
| { |
| return regmap_update_bits(map, reg, mask, mask); |
| } |
| |
| static int anx78xx_clear_bits(struct regmap *map, u8 reg, u8 mask) |
| { |
| return regmap_update_bits(map, reg, mask, 0); |
| } |
| |
| static bool anx78xx_aux_op_finished(struct anx78xx *anx78xx) |
| { |
| unsigned int value; |
| int err; |
| |
| err = regmap_read(anx78xx->map[I2C_IDX_TX_P0], SP_DP_AUX_CH_CTRL2_REG, |
| &value); |
| if (err < 0) |
| return false; |
| |
| return (value & SP_AUX_EN) == 0; |
| } |
| |
| static int anx78xx_aux_wait(struct anx78xx *anx78xx) |
| { |
| unsigned long timeout; |
| unsigned int status; |
| int err; |
| |
| timeout = jiffies + msecs_to_jiffies(AUX_WAIT_TIMEOUT_MS) + 1; |
| |
| while (!anx78xx_aux_op_finished(anx78xx)) { |
| if (time_after(jiffies, timeout)) { |
| if (!anx78xx_aux_op_finished(anx78xx)) { |
| DRM_ERROR("Timed out waiting AUX to finish\n"); |
| return -ETIMEDOUT; |
| } |
| |
| break; |
| } |
| |
| usleep_range(1000, 2000); |
| } |
| |
| /* Read the AUX channel access status */ |
| err = regmap_read(anx78xx->map[I2C_IDX_TX_P0], SP_AUX_CH_STATUS_REG, |
| &status); |
| if (err < 0) { |
| DRM_ERROR("Failed to read from AUX channel: %d\n", err); |
| return err; |
| } |
| |
| if (status & SP_AUX_STATUS) { |
| DRM_ERROR("Failed to wait for AUX channel (status: %02x)\n", |
| status); |
| return -ETIMEDOUT; |
| } |
| |
| return 0; |
| } |
| |
| static int anx78xx_aux_address(struct anx78xx *anx78xx, unsigned int addr) |
| { |
| int err; |
| |
| err = regmap_write(anx78xx->map[I2C_IDX_TX_P0], SP_AUX_ADDR_7_0_REG, |
| addr & 0xff); |
| if (err) |
| return err; |
| |
| err = regmap_write(anx78xx->map[I2C_IDX_TX_P0], SP_AUX_ADDR_15_8_REG, |
| (addr & 0xff00) >> 8); |
| if (err) |
| return err; |
| |
| /* |
| * DP AUX CH Address Register #2, only update bits[3:0] |
| * [7:4] RESERVED |
| * [3:0] AUX_ADDR[19:16], Register control AUX CH address. |
| */ |
| err = regmap_update_bits(anx78xx->map[I2C_IDX_TX_P0], |
| SP_AUX_ADDR_19_16_REG, |
| SP_AUX_ADDR_19_16_MASK, |
| (addr & 0xf0000) >> 16); |
| |
| if (err) |
| return err; |
| |
| return 0; |
| } |
| |
| static ssize_t anx78xx_aux_transfer(struct drm_dp_aux *aux, |
| struct drm_dp_aux_msg *msg) |
| { |
| struct anx78xx *anx78xx = container_of(aux, struct anx78xx, aux); |
| u8 ctrl1 = msg->request; |
| u8 ctrl2 = SP_AUX_EN; |
| u8 *buffer = msg->buffer; |
| int err; |
| |
| /* The DP AUX transmit and receive buffer has 16 bytes. */ |
| if (WARN_ON(msg->size > AUX_CH_BUFFER_SIZE)) |
| return -E2BIG; |
| |
| /* Zero-sized messages specify address-only transactions. */ |
| if (msg->size < 1) |
| ctrl2 |= SP_ADDR_ONLY; |
| else /* For non-zero-sized set the length field. */ |
| ctrl1 |= (msg->size - 1) << SP_AUX_LENGTH_SHIFT; |
| |
| if ((msg->request & DP_AUX_I2C_READ) == 0) { |
| /* When WRITE | MOT write values to data buffer */ |
| err = regmap_bulk_write(anx78xx->map[I2C_IDX_TX_P0], |
| SP_DP_BUF_DATA0_REG, buffer, |
| msg->size); |
| if (err) |
| return err; |
| } |
| |
| /* Write address and request */ |
| err = anx78xx_aux_address(anx78xx, msg->address); |
| if (err) |
| return err; |
| |
| err = regmap_write(anx78xx->map[I2C_IDX_TX_P0], SP_DP_AUX_CH_CTRL1_REG, |
| ctrl1); |
| if (err) |
| return err; |
| |
| /* Start transaction */ |
| err = regmap_update_bits(anx78xx->map[I2C_IDX_TX_P0], |
| SP_DP_AUX_CH_CTRL2_REG, SP_ADDR_ONLY | |
| SP_AUX_EN, ctrl2); |
| if (err) |
| return err; |
| |
| err = anx78xx_aux_wait(anx78xx); |
| if (err) |
| return err; |
| |
| msg->reply = DP_AUX_I2C_REPLY_ACK; |
| |
| if ((msg->size > 0) && (msg->request & DP_AUX_I2C_READ)) { |
| /* Read values from data buffer */ |
| err = regmap_bulk_read(anx78xx->map[I2C_IDX_TX_P0], |
| SP_DP_BUF_DATA0_REG, buffer, |
| msg->size); |
| if (err) |
| return err; |
| } |
| |
| err = anx78xx_clear_bits(anx78xx->map[I2C_IDX_TX_P0], |
| SP_DP_AUX_CH_CTRL2_REG, SP_ADDR_ONLY); |
| if (err) |
| return err; |
| |
| return msg->size; |
| } |
| |
| static int anx78xx_set_hpd(struct anx78xx *anx78xx) |
| { |
| int err; |
| |
| err = anx78xx_clear_bits(anx78xx->map[I2C_IDX_RX_P0], |
| SP_TMDS_CTRL_BASE + 7, SP_PD_RT); |
| if (err) |
| return err; |
| |
| err = anx78xx_set_bits(anx78xx->map[I2C_IDX_TX_P2], SP_VID_CTRL3_REG, |
| SP_HPD_OUT); |
| if (err) |
| return err; |
| |
| return 0; |
| } |
| |
| static int anx78xx_clear_hpd(struct anx78xx *anx78xx) |
| { |
| int err; |
| |
| err = anx78xx_clear_bits(anx78xx->map[I2C_IDX_TX_P2], SP_VID_CTRL3_REG, |
| SP_HPD_OUT); |
| if (err) |
| return err; |
| |
| err = anx78xx_set_bits(anx78xx->map[I2C_IDX_RX_P0], |
| SP_TMDS_CTRL_BASE + 7, SP_PD_RT); |
| if (err) |
| return err; |
| |
| return 0; |
| } |
| |
| static const struct reg_sequence tmds_phy_initialization[] = { |
| { SP_TMDS_CTRL_BASE + 1, 0x90 }, |
| { SP_TMDS_CTRL_BASE + 2, 0xa9 }, |
| { SP_TMDS_CTRL_BASE + 6, 0x92 }, |
| { SP_TMDS_CTRL_BASE + 7, 0x80 }, |
| { SP_TMDS_CTRL_BASE + 20, 0xf2 }, |
| { SP_TMDS_CTRL_BASE + 22, 0xc4 }, |
| { SP_TMDS_CTRL_BASE + 23, 0x18 }, |
| }; |
| |
| static int anx78xx_rx_initialization(struct anx78xx *anx78xx) |
| { |
| int err; |
| |
| err = regmap_write(anx78xx->map[I2C_IDX_RX_P0], SP_HDMI_MUTE_CTRL_REG, |
| SP_AUD_MUTE | SP_VID_MUTE); |
| if (err) |
| return err; |
| |
| err = anx78xx_set_bits(anx78xx->map[I2C_IDX_RX_P0], SP_CHIP_CTRL_REG, |
| SP_MAN_HDMI5V_DET | SP_PLLLOCK_CKDT_EN | |
| SP_DIGITAL_CKDT_EN); |
| if (err) |
| return err; |
| |
| err = anx78xx_set_bits(anx78xx->map[I2C_IDX_RX_P0], |
| SP_SOFTWARE_RESET1_REG, SP_HDCP_MAN_RST | |
| SP_SW_MAN_RST | SP_TMDS_RST | SP_VIDEO_RST); |
| if (err) |
| return err; |
| |
| err = anx78xx_clear_bits(anx78xx->map[I2C_IDX_RX_P0], |
| SP_SOFTWARE_RESET1_REG, SP_HDCP_MAN_RST | |
| SP_SW_MAN_RST | SP_TMDS_RST | SP_VIDEO_RST); |
| if (err) |
| return err; |
| |
| /* Sync detect change, GP set mute */ |
| err = anx78xx_set_bits(anx78xx->map[I2C_IDX_RX_P0], |
| SP_AUD_EXCEPTION_ENABLE_BASE + 1, BIT(5) | |
| BIT(6)); |
| if (err) |
| return err; |
| |
| err = anx78xx_set_bits(anx78xx->map[I2C_IDX_RX_P0], |
| SP_AUD_EXCEPTION_ENABLE_BASE + 3, |
| SP_AEC_EN21); |
| if (err) |
| return err; |
| |
| err = anx78xx_set_bits(anx78xx->map[I2C_IDX_RX_P0], SP_AUDVID_CTRL_REG, |
| SP_AVC_EN | SP_AAC_OE | SP_AAC_EN); |
| if (err) |
| return err; |
| |
| err = anx78xx_clear_bits(anx78xx->map[I2C_IDX_RX_P0], |
| SP_SYSTEM_POWER_DOWN1_REG, SP_PWDN_CTRL); |
| if (err) |
| return err; |
| |
| err = anx78xx_set_bits(anx78xx->map[I2C_IDX_RX_P0], |
| SP_VID_DATA_RANGE_CTRL_REG, SP_R2Y_INPUT_LIMIT); |
| if (err) |
| return err; |
| |
| /* Enable DDC stretch */ |
| err = regmap_write(anx78xx->map[I2C_IDX_TX_P0], |
| SP_DP_EXTRA_I2C_DEV_ADDR_REG, SP_I2C_EXTRA_ADDR); |
| if (err) |
| return err; |
| |
| /* TMDS phy initialization */ |
| err = regmap_multi_reg_write(anx78xx->map[I2C_IDX_RX_P0], |
| tmds_phy_initialization, |
| ARRAY_SIZE(tmds_phy_initialization)); |
| if (err) |
| return err; |
| |
| err = anx78xx_clear_hpd(anx78xx); |
| if (err) |
| return err; |
| |
| return 0; |
| } |
| |
| static const u8 dp_tx_output_precise_tune_bits[20] = { |
| 0x01, 0x03, 0x07, 0x7f, 0x71, 0x6b, 0x7f, |
| 0x73, 0x7f, 0x7f, 0x00, 0x00, 0x00, 0x00, |
| 0x0c, 0x42, 0x1e, 0x3e, 0x72, 0x7e, |
| }; |
| |
| static int anx78xx_link_phy_initialization(struct anx78xx *anx78xx) |
| { |
| int err; |
| |
| /* |
| * REVISIT : It is writing to a RESERVED bits in Analog Control 0 |
| * register. |
| */ |
| err = regmap_write(anx78xx->map[I2C_IDX_TX_P2], SP_ANALOG_CTRL0_REG, |
| 0x02); |
| if (err) |
| return err; |
| |
| /* |
| * Write DP TX output emphasis precise tune bits. |
| */ |
| err = regmap_bulk_write(anx78xx->map[I2C_IDX_TX_P1], |
| SP_DP_TX_LT_CTRL0_REG, |
| dp_tx_output_precise_tune_bits, |
| ARRAY_SIZE(dp_tx_output_precise_tune_bits)); |
| |
| if (err) |
| return err; |
| |
| return 0; |
| } |
| |
| static int anx78xx_xtal_clk_sel(struct anx78xx *anx78xx) |
| { |
| unsigned int value; |
| int err; |
| |
| err = regmap_update_bits(anx78xx->map[I2C_IDX_TX_P2], |
| SP_ANALOG_DEBUG2_REG, |
| SP_XTAL_FRQ | SP_FORCE_SW_OFF_BYPASS, |
| SP_XTAL_FRQ_27M); |
| if (err) |
| return err; |
| |
| err = regmap_write(anx78xx->map[I2C_IDX_TX_P0], SP_DP_AUX_CH_CTRL3_REG, |
| XTAL_CLK & SP_WAIT_COUNTER_7_0_MASK); |
| if (err) |
| return err; |
| |
| err = regmap_write(anx78xx->map[I2C_IDX_TX_P0], SP_DP_AUX_CH_CTRL4_REG, |
| ((XTAL_CLK & 0xff00) >> 2) | (XTAL_CLK / 10)); |
| if (err) |
| return err; |
| |
| err = regmap_write(anx78xx->map[I2C_IDX_TX_P0], |
| SP_I2C_GEN_10US_TIMER0_REG, XTAL_CLK & 0xff); |
| if (err) |
| return err; |
| |
| err = regmap_write(anx78xx->map[I2C_IDX_TX_P0], |
| SP_I2C_GEN_10US_TIMER1_REG, |
| (XTAL_CLK & 0xff00) >> 8); |
| if (err) |
| return err; |
| |
| err = regmap_write(anx78xx->map[I2C_IDX_TX_P0], SP_AUX_MISC_CTRL_REG, |
| XTAL_CLK / 10 - 1); |
| if (err) |
| return err; |
| |
| err = regmap_read(anx78xx->map[I2C_IDX_RX_P0], |
| SP_HDMI_US_TIMER_CTRL_REG, |
| &value); |
| if (err) |
| return err; |
| |
| err = regmap_write(anx78xx->map[I2C_IDX_RX_P0], |
| SP_HDMI_US_TIMER_CTRL_REG, |
| (value & SP_MS_TIMER_MARGIN_10_8_MASK) | |
| ((((XTAL_CLK / 10) >> 1) - 2) << 3)); |
| if (err) |
| return err; |
| |
| return 0; |
| } |
| |
| static const struct reg_sequence otp_key_protect[] = { |
| { SP_OTP_KEY_PROTECT1_REG, SP_OTP_PSW1 }, |
| { SP_OTP_KEY_PROTECT2_REG, SP_OTP_PSW2 }, |
| { SP_OTP_KEY_PROTECT3_REG, SP_OTP_PSW3 }, |
| }; |
| |
| static int anx78xx_tx_initialization(struct anx78xx *anx78xx) |
| { |
| int err; |
| |
| /* Set terminal resistor to 50 ohm */ |
| err = regmap_write(anx78xx->map[I2C_IDX_TX_P0], SP_DP_AUX_CH_CTRL2_REG, |
| 0x30); |
| if (err) |
| return err; |
| |
| /* Enable aux double diff output */ |
| err = anx78xx_set_bits(anx78xx->map[I2C_IDX_TX_P0], |
| SP_DP_AUX_CH_CTRL2_REG, 0x08); |
| if (err) |
| return err; |
| |
| err = anx78xx_clear_bits(anx78xx->map[I2C_IDX_TX_P0], |
| SP_DP_HDCP_CTRL_REG, SP_AUTO_EN | |
| SP_AUTO_START); |
| if (err) |
| return err; |
| |
| err = regmap_multi_reg_write(anx78xx->map[I2C_IDX_TX_P0], |
| otp_key_protect, |
| ARRAY_SIZE(otp_key_protect)); |
| if (err) |
| return err; |
| |
| err = anx78xx_set_bits(anx78xx->map[I2C_IDX_TX_P0], |
| SP_HDCP_KEY_COMMAND_REG, SP_DISABLE_SYNC_HDCP); |
| if (err) |
| return err; |
| |
| err = regmap_write(anx78xx->map[I2C_IDX_TX_P2], SP_VID_CTRL8_REG, |
| SP_VID_VRES_TH); |
| if (err) |
| return err; |
| |
| /* |
| * DP HDCP auto authentication wait timer (when downstream starts to |
| * auth, DP side will wait for this period then do auth automatically) |
| */ |
| err = regmap_write(anx78xx->map[I2C_IDX_TX_P0], SP_HDCP_AUTO_TIMER_REG, |
| 0x00); |
| if (err) |
| return err; |
| |
| err = anx78xx_set_bits(anx78xx->map[I2C_IDX_TX_P0], |
| SP_DP_HDCP_CTRL_REG, SP_LINK_POLLING); |
| if (err) |
| return err; |
| |
| err = anx78xx_set_bits(anx78xx->map[I2C_IDX_TX_P0], |
| SP_DP_LINK_DEBUG_CTRL_REG, SP_M_VID_DEBUG); |
| if (err) |
| return err; |
| |
| err = anx78xx_set_bits(anx78xx->map[I2C_IDX_TX_P2], |
| SP_ANALOG_DEBUG2_REG, SP_POWERON_TIME_1P5MS); |
| if (err) |
| return err; |
| |
| err = anx78xx_xtal_clk_sel(anx78xx); |
| if (err) |
| return err; |
| |
| err = regmap_write(anx78xx->map[I2C_IDX_TX_P0], SP_AUX_DEFER_CTRL_REG, |
| SP_DEFER_CTRL_EN | 0x0c); |
| if (err) |
| return err; |
| |
| err = anx78xx_set_bits(anx78xx->map[I2C_IDX_TX_P0], |
| SP_DP_POLLING_CTRL_REG, |
| SP_AUTO_POLLING_DISABLE); |
| if (err) |
| return err; |
| |
| /* |
| * Short the link integrity check timer to speed up bstatus |
| * polling for HDCP CTS item 1A-07 |
| */ |
| err = regmap_write(anx78xx->map[I2C_IDX_TX_P0], |
| SP_HDCP_LINK_CHECK_TIMER_REG, 0x1d); |
| if (err) |
| return err; |
| |
| err = anx78xx_set_bits(anx78xx->map[I2C_IDX_TX_P0], |
| SP_DP_MISC_CTRL_REG, SP_EQ_TRAINING_LOOP); |
| if (err) |
| return err; |
| |
| /* Power down the main link by default */ |
| err = anx78xx_set_bits(anx78xx->map[I2C_IDX_TX_P0], |
| SP_DP_ANALOG_POWER_DOWN_REG, SP_CH0_PD); |
| if (err) |
| return err; |
| |
| err = anx78xx_link_phy_initialization(anx78xx); |
| if (err) |
| return err; |
| |
| /* Gen m_clk with downspreading */ |
| err = anx78xx_set_bits(anx78xx->map[I2C_IDX_TX_P0], |
| SP_DP_M_CALCULATION_CTRL_REG, SP_M_GEN_CLK_SEL); |
| if (err) |
| return err; |
| |
| return 0; |
| } |
| |
| static int anx78xx_enable_interrupts(struct anx78xx *anx78xx) |
| { |
| int err; |
| |
| /* |
| * BIT0: INT pin assertion polarity: 1 = assert high |
| * BIT1: INT pin output type: 0 = push/pull |
| */ |
| err = regmap_write(anx78xx->map[I2C_IDX_TX_P2], SP_INT_CTRL_REG, 0x01); |
| if (err) |
| return err; |
| |
| err = regmap_write(anx78xx->map[I2C_IDX_TX_P2], |
| SP_COMMON_INT_MASK4_REG, SP_HPD_LOST | SP_HPD_PLUG); |
| if (err) |
| return err; |
| |
| err = regmap_write(anx78xx->map[I2C_IDX_TX_P2], SP_DP_INT_MASK1_REG, |
| SP_TRAINING_FINISH); |
| if (err) |
| return err; |
| |
| err = regmap_write(anx78xx->map[I2C_IDX_RX_P0], SP_INT_MASK1_REG, |
| SP_CKDT_CHG | SP_SCDT_CHG); |
| if (err) |
| return err; |
| |
| return 0; |
| } |
| |
| static void anx78xx_poweron(struct anx78xx *anx78xx) |
| { |
| struct anx78xx_platform_data *pdata = &anx78xx->pdata; |
| int err; |
| |
| if (WARN_ON(anx78xx->powered)) |
| return; |
| |
| if (pdata->dvdd10) { |
| err = regulator_enable(pdata->dvdd10); |
| if (err) { |
| DRM_ERROR("Failed to enable DVDD10 regulator: %d\n", |
| err); |
| return; |
| } |
| |
| usleep_range(1000, 2000); |
| } |
| |
| gpiod_set_value_cansleep(pdata->gpiod_reset, 1); |
| usleep_range(1000, 2000); |
| |
| gpiod_set_value_cansleep(pdata->gpiod_pd, 0); |
| usleep_range(1000, 2000); |
| |
| gpiod_set_value_cansleep(pdata->gpiod_reset, 0); |
| |
| /* Power on registers module */ |
| anx78xx_set_bits(anx78xx->map[I2C_IDX_TX_P2], SP_POWERDOWN_CTRL_REG, |
| SP_HDCP_PD | SP_AUDIO_PD | SP_VIDEO_PD | SP_LINK_PD); |
| anx78xx_clear_bits(anx78xx->map[I2C_IDX_TX_P2], SP_POWERDOWN_CTRL_REG, |
| SP_REGISTER_PD | SP_TOTAL_PD); |
| |
| anx78xx->powered = true; |
| } |
| |
| static void anx78xx_poweroff(struct anx78xx *anx78xx) |
| { |
| struct anx78xx_platform_data *pdata = &anx78xx->pdata; |
| int err; |
| |
| if (WARN_ON(!anx78xx->powered)) |
| return; |
| |
| gpiod_set_value_cansleep(pdata->gpiod_reset, 1); |
| usleep_range(1000, 2000); |
| |
| gpiod_set_value_cansleep(pdata->gpiod_pd, 1); |
| usleep_range(1000, 2000); |
| |
| if (pdata->dvdd10) { |
| err = regulator_disable(pdata->dvdd10); |
| if (err) { |
| DRM_ERROR("Failed to disable DVDD10 regulator: %d\n", |
| err); |
| return; |
| } |
| |
| usleep_range(1000, 2000); |
| } |
| |
| anx78xx->powered = false; |
| } |
| |
| static int anx78xx_start(struct anx78xx *anx78xx) |
| { |
| int err; |
| |
| /* Power on all modules */ |
| err = anx78xx_clear_bits(anx78xx->map[I2C_IDX_TX_P2], |
| SP_POWERDOWN_CTRL_REG, |
| SP_HDCP_PD | SP_AUDIO_PD | SP_VIDEO_PD | |
| SP_LINK_PD); |
| |
| err = anx78xx_enable_interrupts(anx78xx); |
| if (err) { |
| DRM_ERROR("Failed to enable interrupts: %d\n", err); |
| goto err_poweroff; |
| } |
| |
| err = anx78xx_rx_initialization(anx78xx); |
| if (err) { |
| DRM_ERROR("Failed receiver initialization: %d\n", err); |
| goto err_poweroff; |
| } |
| |
| err = anx78xx_tx_initialization(anx78xx); |
| if (err) { |
| DRM_ERROR("Failed transmitter initialization: %d\n", err); |
| goto err_poweroff; |
| } |
| |
| /* |
| * This delay seems to help keep the hardware in a good state. Without |
| * it, there are times where it fails silently. |
| */ |
| usleep_range(10000, 15000); |
| |
| return 0; |
| |
| err_poweroff: |
| DRM_ERROR("Failed SlimPort transmitter initialization: %d\n", err); |
| anx78xx_poweroff(anx78xx); |
| |
| return err; |
| } |
| |
| static int anx78xx_init_pdata(struct anx78xx *anx78xx) |
| { |
| struct anx78xx_platform_data *pdata = &anx78xx->pdata; |
| struct device *dev = &anx78xx->client->dev; |
| |
| /* 1.0V digital core power regulator */ |
| pdata->dvdd10 = devm_regulator_get(dev, "dvdd10"); |
| if (IS_ERR(pdata->dvdd10)) { |
| DRM_ERROR("DVDD10 regulator not found\n"); |
| return PTR_ERR(pdata->dvdd10); |
| } |
| |
| /* GPIO for HPD */ |
| pdata->gpiod_hpd = devm_gpiod_get(dev, "hpd", GPIOD_IN); |
| if (IS_ERR(pdata->gpiod_hpd)) |
| return PTR_ERR(pdata->gpiod_hpd); |
| |
| /* GPIO for chip power down */ |
| pdata->gpiod_pd = devm_gpiod_get(dev, "pd", GPIOD_OUT_HIGH); |
| if (IS_ERR(pdata->gpiod_pd)) |
| return PTR_ERR(pdata->gpiod_pd); |
| |
| /* GPIO for chip reset */ |
| pdata->gpiod_reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); |
| |
| return PTR_ERR_OR_ZERO(pdata->gpiod_reset); |
| } |
| |
| static int anx78xx_dp_link_training(struct anx78xx *anx78xx) |
| { |
| u8 dp_bw, value; |
| int err; |
| |
| err = regmap_write(anx78xx->map[I2C_IDX_RX_P0], SP_HDMI_MUTE_CTRL_REG, |
| 0x0); |
| if (err) |
| return err; |
| |
| err = anx78xx_clear_bits(anx78xx->map[I2C_IDX_TX_P2], |
| SP_POWERDOWN_CTRL_REG, |
| SP_TOTAL_PD); |
| if (err) |
| return err; |
| |
| err = drm_dp_dpcd_readb(&anx78xx->aux, DP_MAX_LINK_RATE, &dp_bw); |
| if (err < 0) |
| return err; |
| |
| switch (dp_bw) { |
| case DP_LINK_BW_1_62: |
| case DP_LINK_BW_2_7: |
| case DP_LINK_BW_5_4: |
| break; |
| |
| default: |
| DRM_DEBUG_KMS("DP bandwidth (%#02x) not supported\n", dp_bw); |
| return -EINVAL; |
| } |
| |
| err = anx78xx_set_bits(anx78xx->map[I2C_IDX_TX_P2], SP_VID_CTRL1_REG, |
| SP_VIDEO_MUTE); |
| if (err) |
| return err; |
| |
| err = anx78xx_clear_bits(anx78xx->map[I2C_IDX_TX_P2], |
| SP_VID_CTRL1_REG, SP_VIDEO_EN); |
| if (err) |
| return err; |
| |
| /* Get DPCD info */ |
| err = drm_dp_dpcd_read(&anx78xx->aux, DP_DPCD_REV, |
| &anx78xx->dpcd, DP_RECEIVER_CAP_SIZE); |
| if (err < 0) { |
| DRM_ERROR("Failed to read DPCD: %d\n", err); |
| return err; |
| } |
| |
| /* Clear channel x SERDES power down */ |
| err = anx78xx_clear_bits(anx78xx->map[I2C_IDX_TX_P0], |
| SP_DP_ANALOG_POWER_DOWN_REG, SP_CH0_PD); |
| if (err) |
| return err; |
| |
| /* Check link capabilities */ |
| err = drm_dp_link_probe(&anx78xx->aux, &anx78xx->link); |
| if (err < 0) { |
| DRM_ERROR("Failed to probe link capabilities: %d\n", err); |
| return err; |
| } |
| |
| /* Power up the sink */ |
| err = drm_dp_link_power_up(&anx78xx->aux, &anx78xx->link); |
| if (err < 0) { |
| DRM_ERROR("Failed to power up DisplayPort link: %d\n", err); |
| return err; |
| } |
| |
| /* Possibly enable downspread on the sink */ |
| err = regmap_write(anx78xx->map[I2C_IDX_TX_P0], |
| SP_DP_DOWNSPREAD_CTRL1_REG, 0); |
| if (err) |
| return err; |
| |
| if (anx78xx->dpcd[DP_MAX_DOWNSPREAD] & DP_MAX_DOWNSPREAD_0_5) { |
| DRM_DEBUG("Enable downspread on the sink\n"); |
| /* 4000PPM */ |
| err = regmap_write(anx78xx->map[I2C_IDX_TX_P0], |
| SP_DP_DOWNSPREAD_CTRL1_REG, 8); |
| if (err) |
| return err; |
| |
| err = drm_dp_dpcd_writeb(&anx78xx->aux, DP_DOWNSPREAD_CTRL, |
| DP_SPREAD_AMP_0_5); |
| if (err < 0) |
| return err; |
| } else { |
| err = drm_dp_dpcd_writeb(&anx78xx->aux, DP_DOWNSPREAD_CTRL, 0); |
| if (err < 0) |
| return err; |
| } |
| |
| /* Set the lane count and the link rate on the sink */ |
| if (drm_dp_enhanced_frame_cap(anx78xx->dpcd)) |
| err = anx78xx_set_bits(anx78xx->map[I2C_IDX_TX_P0], |
| SP_DP_SYSTEM_CTRL_BASE + 4, |
| SP_ENHANCED_MODE); |
| else |
| err = anx78xx_clear_bits(anx78xx->map[I2C_IDX_TX_P0], |
| SP_DP_SYSTEM_CTRL_BASE + 4, |
| SP_ENHANCED_MODE); |
| if (err) |
| return err; |
| |
| value = drm_dp_link_rate_to_bw_code(anx78xx->link.rate); |
| err = regmap_write(anx78xx->map[I2C_IDX_TX_P0], |
| SP_DP_MAIN_LINK_BW_SET_REG, value); |
| if (err) |
| return err; |
| |
| err = drm_dp_link_configure(&anx78xx->aux, &anx78xx->link); |
| if (err < 0) { |
| DRM_ERROR("Failed to configure DisplayPort link: %d\n", err); |
| return err; |
| } |
| |
| /* Start training on the source */ |
| err = regmap_write(anx78xx->map[I2C_IDX_TX_P0], SP_DP_LT_CTRL_REG, |
| SP_LT_EN); |
| if (err) |
| return err; |
| |
| return 0; |
| } |
| |
| static int anx78xx_config_dp_output(struct anx78xx *anx78xx) |
| { |
| int err; |
| |
| err = anx78xx_clear_bits(anx78xx->map[I2C_IDX_TX_P2], SP_VID_CTRL1_REG, |
| SP_VIDEO_MUTE); |
| if (err) |
| return err; |
| |
| /* Enable DP output */ |
| err = anx78xx_set_bits(anx78xx->map[I2C_IDX_TX_P2], SP_VID_CTRL1_REG, |
| SP_VIDEO_EN); |
| if (err) |
| return err; |
| |
| return 0; |
| } |
| |
| static int anx78xx_send_video_infoframe(struct anx78xx *anx78xx, |
| struct hdmi_avi_infoframe *frame) |
| { |
| u8 buffer[HDMI_INFOFRAME_HEADER_SIZE + HDMI_AVI_INFOFRAME_SIZE]; |
| int err; |
| |
| err = hdmi_avi_infoframe_pack(frame, buffer, sizeof(buffer)); |
| if (err < 0) { |
| DRM_ERROR("Failed to pack AVI infoframe: %d\n", err); |
| return err; |
| } |
| |
| err = anx78xx_clear_bits(anx78xx->map[I2C_IDX_TX_P0], |
| SP_PACKET_SEND_CTRL_REG, SP_AVI_IF_EN); |
| if (err) |
| return err; |
| |
| err = regmap_bulk_write(anx78xx->map[I2C_IDX_TX_P2], |
| SP_INFOFRAME_AVI_DB1_REG, buffer, |
| frame->length); |
| if (err) |
| return err; |
| |
| err = anx78xx_set_bits(anx78xx->map[I2C_IDX_TX_P0], |
| SP_PACKET_SEND_CTRL_REG, SP_AVI_IF_UD); |
| if (err) |
| return err; |
| |
| err = anx78xx_set_bits(anx78xx->map[I2C_IDX_TX_P0], |
| SP_PACKET_SEND_CTRL_REG, SP_AVI_IF_EN); |
| if (err) |
| return err; |
| |
| return 0; |
| } |
| |
| static int anx78xx_get_downstream_info(struct anx78xx *anx78xx) |
| { |
| u8 value; |
| int err; |
| |
| err = drm_dp_dpcd_readb(&anx78xx->aux, DP_SINK_COUNT, &value); |
| if (err < 0) { |
| DRM_ERROR("Get sink count failed %d\n", err); |
| return err; |
| } |
| |
| if (!DP_GET_SINK_COUNT(value)) { |
| DRM_ERROR("Downstream disconnected\n"); |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| static int anx78xx_get_modes(struct drm_connector *connector) |
| { |
| struct anx78xx *anx78xx = connector_to_anx78xx(connector); |
| int err, num_modes = 0; |
| |
| if (WARN_ON(!anx78xx->powered)) |
| return 0; |
| |
| if (anx78xx->edid) |
| return drm_add_edid_modes(connector, anx78xx->edid); |
| |
| mutex_lock(&anx78xx->lock); |
| |
| err = anx78xx_get_downstream_info(anx78xx); |
| if (err) { |
| DRM_ERROR("Failed to get downstream info: %d\n", err); |
| goto unlock; |
| } |
| |
| anx78xx->edid = drm_get_edid(connector, &anx78xx->aux.ddc); |
| if (!anx78xx->edid) { |
| DRM_ERROR("Failed to read EDID\n"); |
| goto unlock; |
| } |
| |
| err = drm_mode_connector_update_edid_property(connector, |
| anx78xx->edid); |
| if (err) { |
| DRM_ERROR("Failed to update EDID property: %d\n", err); |
| goto unlock; |
| } |
| |
| num_modes = drm_add_edid_modes(connector, anx78xx->edid); |
| /* Store the ELD */ |
| drm_edid_to_eld(connector, anx78xx->edid); |
| |
| unlock: |
| mutex_unlock(&anx78xx->lock); |
| |
| return num_modes; |
| } |
| |
| static const struct drm_connector_helper_funcs anx78xx_connector_helper_funcs = { |
| .get_modes = anx78xx_get_modes, |
| }; |
| |
| static enum drm_connector_status anx78xx_detect(struct drm_connector *connector, |
| bool force) |
| { |
| struct anx78xx *anx78xx = connector_to_anx78xx(connector); |
| |
| if (!gpiod_get_value(anx78xx->pdata.gpiod_hpd)) |
| return connector_status_disconnected; |
| |
| return connector_status_connected; |
| } |
| |
| static void anx78xx_connector_destroy(struct drm_connector *connector) |
| { |
| drm_connector_cleanup(connector); |
| } |
| |
| static const struct drm_connector_funcs anx78xx_connector_funcs = { |
| .dpms = drm_atomic_helper_connector_dpms, |
| .fill_modes = drm_helper_probe_single_connector_modes, |
| .detect = anx78xx_detect, |
| .destroy = anx78xx_connector_destroy, |
| .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 anx78xx_bridge_attach(struct drm_bridge *bridge) |
| { |
| struct anx78xx *anx78xx = bridge_to_anx78xx(bridge); |
| int err; |
| |
| if (!bridge->encoder) { |
| DRM_ERROR("Parent encoder object not found"); |
| return -ENODEV; |
| } |
| |
| /* Register aux channel */ |
| anx78xx->aux.name = "DP-AUX"; |
| anx78xx->aux.dev = &anx78xx->client->dev; |
| anx78xx->aux.transfer = anx78xx_aux_transfer; |
| |
| err = drm_dp_aux_register(&anx78xx->aux); |
| if (err < 0) { |
| DRM_ERROR("Failed to register aux channel: %d\n", err); |
| return err; |
| } |
| |
| err = drm_connector_init(bridge->dev, &anx78xx->connector, |
| &anx78xx_connector_funcs, |
| DRM_MODE_CONNECTOR_DisplayPort); |
| if (err) { |
| DRM_ERROR("Failed to initialize connector: %d\n", err); |
| return err; |
| } |
| |
| drm_connector_helper_add(&anx78xx->connector, |
| &anx78xx_connector_helper_funcs); |
| |
| err = drm_connector_register(&anx78xx->connector); |
| if (err) { |
| DRM_ERROR("Failed to register connector: %d\n", err); |
| return err; |
| } |
| |
| anx78xx->connector.polled = DRM_CONNECTOR_POLL_HPD; |
| |
| err = drm_mode_connector_attach_encoder(&anx78xx->connector, |
| bridge->encoder); |
| if (err) { |
| DRM_ERROR("Failed to link up connector to encoder: %d\n", err); |
| return err; |
| } |
| |
| return 0; |
| } |
| |
| static bool anx78xx_bridge_mode_fixup(struct drm_bridge *bridge, |
| const struct drm_display_mode *mode, |
| struct drm_display_mode *adjusted_mode) |
| { |
| if (mode->flags & DRM_MODE_FLAG_INTERLACE) |
| return false; |
| |
| /* Max 1200p at 5.4 Ghz, one lane */ |
| if (mode->clock > 154000) |
| return false; |
| |
| return true; |
| } |
| |
| static void anx78xx_bridge_disable(struct drm_bridge *bridge) |
| { |
| struct anx78xx *anx78xx = bridge_to_anx78xx(bridge); |
| |
| /* Power off all modules except configuration registers access */ |
| anx78xx_set_bits(anx78xx->map[I2C_IDX_TX_P2], SP_POWERDOWN_CTRL_REG, |
| SP_HDCP_PD | SP_AUDIO_PD | SP_VIDEO_PD | SP_LINK_PD); |
| } |
| |
| static void anx78xx_bridge_mode_set(struct drm_bridge *bridge, |
| struct drm_display_mode *mode, |
| struct drm_display_mode *adjusted_mode) |
| { |
| struct anx78xx *anx78xx = bridge_to_anx78xx(bridge); |
| struct hdmi_avi_infoframe frame; |
| int err; |
| |
| if (WARN_ON(!anx78xx->powered)) |
| return; |
| |
| mutex_lock(&anx78xx->lock); |
| |
| err = drm_hdmi_avi_infoframe_from_display_mode(&frame, adjusted_mode); |
| if (err) { |
| DRM_ERROR("Failed to setup AVI infoframe: %d\n", err); |
| goto unlock; |
| } |
| |
| err = anx78xx_send_video_infoframe(anx78xx, &frame); |
| if (err) |
| DRM_ERROR("Failed to send AVI infoframe: %d\n", err); |
| |
| unlock: |
| mutex_unlock(&anx78xx->lock); |
| } |
| |
| static void anx78xx_bridge_enable(struct drm_bridge *bridge) |
| { |
| struct anx78xx *anx78xx = bridge_to_anx78xx(bridge); |
| int err; |
| |
| err = anx78xx_start(anx78xx); |
| if (err) { |
| DRM_ERROR("Failed to initialize: %d\n", err); |
| return; |
| } |
| |
| err = anx78xx_set_hpd(anx78xx); |
| if (err) |
| DRM_ERROR("Failed to set HPD: %d\n", err); |
| } |
| |
| static const struct drm_bridge_funcs anx78xx_bridge_funcs = { |
| .attach = anx78xx_bridge_attach, |
| .mode_fixup = anx78xx_bridge_mode_fixup, |
| .disable = anx78xx_bridge_disable, |
| .mode_set = anx78xx_bridge_mode_set, |
| .enable = anx78xx_bridge_enable, |
| }; |
| |
| static irqreturn_t anx78xx_hpd_threaded_handler(int irq, void *data) |
| { |
| struct anx78xx *anx78xx = data; |
| int err; |
| |
| if (anx78xx->powered) |
| return IRQ_HANDLED; |
| |
| mutex_lock(&anx78xx->lock); |
| |
| /* Cable is pulled, power on the chip */ |
| anx78xx_poweron(anx78xx); |
| |
| err = anx78xx_enable_interrupts(anx78xx); |
| if (err) |
| DRM_ERROR("Failed to enable interrupts: %d\n", err); |
| |
| mutex_unlock(&anx78xx->lock); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static int anx78xx_handle_dp_int_1(struct anx78xx *anx78xx, u8 irq) |
| { |
| int err; |
| |
| DRM_DEBUG_KMS("Handle DP interrupt 1: %02x\n", irq); |
| |
| err = regmap_write(anx78xx->map[I2C_IDX_TX_P2], SP_DP_INT_STATUS1_REG, |
| irq); |
| if (err) |
| return err; |
| |
| if (irq & SP_TRAINING_FINISH) { |
| DRM_DEBUG_KMS("IRQ: hardware link training finished\n"); |
| err = anx78xx_config_dp_output(anx78xx); |
| } |
| |
| return err; |
| } |
| |
| static bool anx78xx_handle_common_int_4(struct anx78xx *anx78xx, u8 irq) |
| { |
| bool event = false; |
| int err; |
| |
| DRM_DEBUG_KMS("Handle common interrupt 4: %02x\n", irq); |
| |
| err = regmap_write(anx78xx->map[I2C_IDX_TX_P2], |
| SP_COMMON_INT_STATUS4_REG, irq); |
| if (err) { |
| DRM_ERROR("Failed to write SP_COMMON_INT_STATUS4 %d\n", err); |
| return event; |
| } |
| |
| if (irq & SP_HPD_LOST) { |
| DRM_DEBUG_KMS("IRQ: Hot plug detect - cable is pulled out\n"); |
| event = true; |
| anx78xx_poweroff(anx78xx); |
| /* Free cached EDID */ |
| kfree(anx78xx->edid); |
| anx78xx->edid = NULL; |
| } else if (irq & SP_HPD_PLUG) { |
| DRM_DEBUG_KMS("IRQ: Hot plug detect - cable plug\n"); |
| event = true; |
| } |
| |
| return event; |
| } |
| |
| static void anx78xx_handle_hdmi_int_1(struct anx78xx *anx78xx, u8 irq) |
| { |
| unsigned int value; |
| int err; |
| |
| DRM_DEBUG_KMS("Handle HDMI interrupt 1: %02x\n", irq); |
| |
| err = regmap_write(anx78xx->map[I2C_IDX_RX_P0], SP_INT_STATUS1_REG, |
| irq); |
| if (err) { |
| DRM_ERROR("Write HDMI int 1 failed: %d\n", err); |
| return; |
| } |
| |
| if ((irq & SP_CKDT_CHG) || (irq & SP_SCDT_CHG)) { |
| DRM_DEBUG_KMS("IRQ: HDMI input detected\n"); |
| |
| err = regmap_read(anx78xx->map[I2C_IDX_RX_P0], |
| SP_SYSTEM_STATUS_REG, &value); |
| if (err) { |
| DRM_ERROR("Read system status reg failed: %d\n", err); |
| return; |
| } |
| |
| if (!(value & SP_TMDS_CLOCK_DET)) { |
| DRM_DEBUG_KMS("IRQ: *** Waiting for HDMI clock ***\n"); |
| return; |
| } |
| |
| if (!(value & SP_TMDS_DE_DET)) { |
| DRM_DEBUG_KMS("IRQ: *** Waiting for HDMI signal ***\n"); |
| return; |
| } |
| |
| err = anx78xx_dp_link_training(anx78xx); |
| if (err) |
| DRM_ERROR("Failed to start link training: %d\n", err); |
| } |
| } |
| |
| static irqreturn_t anx78xx_intp_threaded_handler(int unused, void *data) |
| { |
| struct anx78xx *anx78xx = data; |
| bool event = false; |
| unsigned int irq; |
| int err; |
| |
| mutex_lock(&anx78xx->lock); |
| |
| err = regmap_read(anx78xx->map[I2C_IDX_TX_P2], SP_DP_INT_STATUS1_REG, |
| &irq); |
| if (err) { |
| DRM_ERROR("Failed to read DP interrupt 1 status: %d\n", err); |
| goto unlock; |
| } |
| |
| if (irq) |
| anx78xx_handle_dp_int_1(anx78xx, irq); |
| |
| err = regmap_read(anx78xx->map[I2C_IDX_TX_P2], |
| SP_COMMON_INT_STATUS4_REG, &irq); |
| if (err) { |
| DRM_ERROR("Failed to read common interrupt 4 status: %d\n", |
| err); |
| goto unlock; |
| } |
| |
| if (irq) |
| event = anx78xx_handle_common_int_4(anx78xx, irq); |
| |
| /* Make sure we are still powered after handle HPD events */ |
| if (!anx78xx->powered) |
| goto unlock; |
| |
| err = regmap_read(anx78xx->map[I2C_IDX_RX_P0], SP_INT_STATUS1_REG, |
| &irq); |
| if (err) { |
| DRM_ERROR("Failed to read HDMI int 1 status: %d\n", err); |
| goto unlock; |
| } |
| |
| if (irq) |
| anx78xx_handle_hdmi_int_1(anx78xx, irq); |
| |
| unlock: |
| mutex_unlock(&anx78xx->lock); |
| |
| if (event) |
| drm_helper_hpd_irq_event(anx78xx->connector.dev); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static void unregister_i2c_dummy_clients(struct anx78xx *anx78xx) |
| { |
| unsigned int i; |
| |
| for (i = 0; i < ARRAY_SIZE(anx78xx->i2c_dummy); i++) |
| if (anx78xx->i2c_dummy[i]) |
| i2c_unregister_device(anx78xx->i2c_dummy[i]); |
| } |
| |
| static const struct regmap_config anx78xx_regmap_config = { |
| .reg_bits = 8, |
| .val_bits = 8, |
| }; |
| |
| static const u16 anx78xx_chipid_list[] = { |
| 0x7812, |
| 0x7814, |
| 0x7818, |
| }; |
| |
| static int anx78xx_i2c_probe(struct i2c_client *client, |
| const struct i2c_device_id *id) |
| { |
| struct anx78xx *anx78xx; |
| struct anx78xx_platform_data *pdata; |
| unsigned int i, idl, idh, version; |
| bool found = false; |
| int err; |
| |
| anx78xx = devm_kzalloc(&client->dev, sizeof(*anx78xx), GFP_KERNEL); |
| if (!anx78xx) |
| return -ENOMEM; |
| |
| pdata = &anx78xx->pdata; |
| |
| mutex_init(&anx78xx->lock); |
| |
| #if IS_ENABLED(CONFIG_OF) |
| anx78xx->bridge.of_node = client->dev.of_node; |
| #endif |
| |
| anx78xx->client = client; |
| i2c_set_clientdata(client, anx78xx); |
| |
| err = anx78xx_init_pdata(anx78xx); |
| if (err) { |
| DRM_ERROR("Failed to initialize pdata: %d\n", err); |
| return err; |
| } |
| |
| pdata->hpd_irq = gpiod_to_irq(pdata->gpiod_hpd); |
| if (pdata->hpd_irq < 0) { |
| DRM_ERROR("Failed to get HPD IRQ: %d\n", pdata->hpd_irq); |
| return -ENODEV; |
| } |
| |
| pdata->intp_irq = client->irq; |
| if (!pdata->intp_irq) { |
| DRM_ERROR("Failed to get CABLE_DET and INTP IRQ\n"); |
| return -ENODEV; |
| } |
| |
| /* Map slave addresses of ANX7814 */ |
| for (i = 0; i < I2C_NUM_ADDRESSES; i++) { |
| anx78xx->i2c_dummy[i] = i2c_new_dummy(client->adapter, |
| anx78xx_i2c_addresses[i] >> 1); |
| if (!anx78xx->i2c_dummy[i]) { |
| err = -ENOMEM; |
| DRM_ERROR("Failed to reserve I2C bus %02x\n", |
| anx78xx_i2c_addresses[i]); |
| goto err_unregister_i2c; |
| } |
| |
| anx78xx->map[i] = devm_regmap_init_i2c(anx78xx->i2c_dummy[i], |
| &anx78xx_regmap_config); |
| if (IS_ERR(anx78xx->map[i])) { |
| err = PTR_ERR(anx78xx->map[i]); |
| DRM_ERROR("Failed regmap initialization %02x\n", |
| anx78xx_i2c_addresses[i]); |
| goto err_unregister_i2c; |
| } |
| } |
| |
| /* Look for supported chip ID */ |
| anx78xx_poweron(anx78xx); |
| |
| err = regmap_read(anx78xx->map[I2C_IDX_TX_P2], SP_DEVICE_IDL_REG, |
| &idl); |
| if (err) |
| goto err_poweroff; |
| |
| err = regmap_read(anx78xx->map[I2C_IDX_TX_P2], SP_DEVICE_IDH_REG, |
| &idh); |
| if (err) |
| goto err_poweroff; |
| |
| anx78xx->chipid = (u8)idl | ((u8)idh << 8); |
| |
| err = regmap_read(anx78xx->map[I2C_IDX_TX_P2], SP_DEVICE_VERSION_REG, |
| &version); |
| if (err) |
| goto err_poweroff; |
| |
| for (i = 0; i < ARRAY_SIZE(anx78xx_chipid_list); i++) { |
| if (anx78xx->chipid == anx78xx_chipid_list[i]) { |
| DRM_INFO("Found ANX%x (ver. %d) SlimPort Transmitter\n", |
| anx78xx->chipid, version); |
| found = true; |
| break; |
| } |
| } |
| |
| if (!found) { |
| DRM_ERROR("ANX%x (ver. %d) not supported by this driver\n", |
| anx78xx->chipid, version); |
| err = -ENODEV; |
| goto err_poweroff; |
| } |
| |
| err = devm_request_threaded_irq(&client->dev, pdata->hpd_irq, NULL, |
| anx78xx_hpd_threaded_handler, |
| IRQF_TRIGGER_RISING | IRQF_ONESHOT, |
| "anx78xx-hpd", anx78xx); |
| if (err) { |
| DRM_ERROR("Failed to request CABLE_DET threaded IRQ: %d\n", |
| err); |
| goto err_poweroff; |
| } |
| |
| err = devm_request_threaded_irq(&client->dev, pdata->intp_irq, NULL, |
| anx78xx_intp_threaded_handler, |
| IRQF_TRIGGER_RISING | IRQF_ONESHOT, |
| "anx78xx-intp", anx78xx); |
| if (err) { |
| DRM_ERROR("Failed to request INTP threaded IRQ: %d\n", err); |
| goto err_poweroff; |
| } |
| |
| anx78xx->bridge.funcs = &anx78xx_bridge_funcs; |
| |
| err = drm_bridge_add(&anx78xx->bridge); |
| if (err < 0) { |
| DRM_ERROR("Failed to add drm bridge: %d\n", err); |
| goto err_poweroff; |
| } |
| |
| /* If cable is pulled out, just poweroff and wait for HPD event */ |
| if (!gpiod_get_value(anx78xx->pdata.gpiod_hpd)) |
| anx78xx_poweroff(anx78xx); |
| |
| return 0; |
| |
| err_poweroff: |
| anx78xx_poweroff(anx78xx); |
| |
| err_unregister_i2c: |
| unregister_i2c_dummy_clients(anx78xx); |
| return err; |
| } |
| |
| static int anx78xx_i2c_remove(struct i2c_client *client) |
| { |
| struct anx78xx *anx78xx = i2c_get_clientdata(client); |
| |
| drm_bridge_remove(&anx78xx->bridge); |
| |
| unregister_i2c_dummy_clients(anx78xx); |
| |
| kfree(anx78xx->edid); |
| |
| return 0; |
| } |
| |
| static const struct i2c_device_id anx78xx_id[] = { |
| { "anx7814", 0 }, |
| { /* sentinel */ } |
| }; |
| MODULE_DEVICE_TABLE(i2c, anx78xx_id); |
| |
| #if IS_ENABLED(CONFIG_OF) |
| static const struct of_device_id anx78xx_match_table[] = { |
| { .compatible = "analogix,anx7814", }, |
| { /* sentinel */ }, |
| }; |
| MODULE_DEVICE_TABLE(of, anx78xx_match_table); |
| #endif |
| |
| static struct i2c_driver anx78xx_driver = { |
| .driver = { |
| .name = "anx7814", |
| .of_match_table = of_match_ptr(anx78xx_match_table), |
| }, |
| .probe = anx78xx_i2c_probe, |
| .remove = anx78xx_i2c_remove, |
| .id_table = anx78xx_id, |
| }; |
| module_i2c_driver(anx78xx_driver); |
| |
| MODULE_DESCRIPTION("ANX78xx SlimPort Transmitter driver"); |
| MODULE_AUTHOR("Enric Balletbo i Serra <enric.balletbo@collabora.com>"); |
| MODULE_LICENSE("GPL v2"); |