966 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * Copyright (C) 2017 Samsung Electronics4 *5 * Authors:6 * Tomasz Stanislawski <t.stanislaws@samsung.com>7 * Maciej Purski <m.purski@samsung.com>8 *9 * Based on sii9234 driver created by:10 * Adam Hampson <ahampson@sta.samsung.com>11 * Erik Gilling <konkers@android.com>12 * Shankar Bandal <shankar.b@samsung.com>13 * Dharam Kumar <dharam.kr@samsung.com>14 */15#include <drm/bridge/mhl.h>16#include <drm/drm_bridge.h>17#include <drm/drm_crtc.h>18#include <drm/drm_edid.h>19 20#include <linux/delay.h>21#include <linux/err.h>22#include <linux/gpio/consumer.h>23#include <linux/i2c.h>24#include <linux/interrupt.h>25#include <linux/irq.h>26#include <linux/kernel.h>27#include <linux/module.h>28#include <linux/mutex.h>29#include <linux/regulator/consumer.h>30#include <linux/slab.h>31 32#define CBUS_DEVCAP_OFFSET 0x8033 34#define SII9234_MHL_VERSION 0x1135#define SII9234_SCRATCHPAD_SIZE 0x1036#define SII9234_INT_STAT_SIZE 0x3337 38#define BIT_TMDS_CCTRL_TMDS_OE BIT(4)39#define MHL_HPD_OUT_OVR_EN BIT(4)40#define MHL_HPD_OUT_OVR_VAL BIT(5)41#define MHL_INIT_TIMEOUT 0x0C42 43/* MHL Tx registers and bits */44#define MHL_TX_SRST 0x0545#define MHL_TX_SYSSTAT_REG 0x0946#define MHL_TX_INTR1_REG 0x7147#define MHL_TX_INTR4_REG 0x7448#define MHL_TX_INTR1_ENABLE_REG 0x7549#define MHL_TX_INTR4_ENABLE_REG 0x7850#define MHL_TX_INT_CTRL_REG 0x7951#define MHL_TX_TMDS_CCTRL 0x8052#define MHL_TX_DISC_CTRL1_REG 0x9053#define MHL_TX_DISC_CTRL2_REG 0x9154#define MHL_TX_DISC_CTRL3_REG 0x9255#define MHL_TX_DISC_CTRL4_REG 0x9356#define MHL_TX_DISC_CTRL5_REG 0x9457#define MHL_TX_DISC_CTRL6_REG 0x9558#define MHL_TX_DISC_CTRL7_REG 0x9659#define MHL_TX_DISC_CTRL8_REG 0x9760#define MHL_TX_STAT2_REG 0x9961#define MHL_TX_MHLTX_CTL1_REG 0xA062#define MHL_TX_MHLTX_CTL2_REG 0xA163#define MHL_TX_MHLTX_CTL4_REG 0xA364#define MHL_TX_MHLTX_CTL6_REG 0xA565#define MHL_TX_MHLTX_CTL7_REG 0xA666 67#define RSEN_STATUS BIT(2)68#define HPD_CHANGE_INT BIT(6)69#define RSEN_CHANGE_INT BIT(5)70#define RGND_READY_INT BIT(6)71#define VBUS_LOW_INT BIT(5)72#define CBUS_LKOUT_INT BIT(4)73#define MHL_DISC_FAIL_INT BIT(3)74#define MHL_EST_INT BIT(2)75#define HPD_CHANGE_INT_MASK BIT(6)76#define RSEN_CHANGE_INT_MASK BIT(5)77 78#define RGND_READY_MASK BIT(6)79#define CBUS_LKOUT_MASK BIT(4)80#define MHL_DISC_FAIL_MASK BIT(3)81#define MHL_EST_MASK BIT(2)82 83#define SKIP_GND BIT(6)84 85#define ATT_THRESH_SHIFT 0x0486#define ATT_THRESH_MASK (0x03 << ATT_THRESH_SHIFT)87#define USB_D_OEN BIT(3)88#define DEGLITCH_TIME_MASK 0x0789#define DEGLITCH_TIME_2MS 090#define DEGLITCH_TIME_4MS 191#define DEGLITCH_TIME_8MS 292#define DEGLITCH_TIME_16MS 393#define DEGLITCH_TIME_40MS 494#define DEGLITCH_TIME_50MS 595#define DEGLITCH_TIME_60MS 696#define DEGLITCH_TIME_128MS 797 98#define USB_D_OVR BIT(7)99#define USB_ID_OVR BIT(6)100#define DVRFLT_SEL BIT(5)101#define BLOCK_RGND_INT BIT(4)102#define SKIP_DEG BIT(3)103#define CI2CA_POL BIT(2)104#define CI2CA_WKUP BIT(1)105#define SINGLE_ATT BIT(0)106 107#define USB_D_ODN BIT(5)108#define VBUS_CHECK BIT(2)109#define RGND_INTP_MASK 0x03110#define RGND_INTP_OPEN 0111#define RGND_INTP_2K 1112#define RGND_INTP_1K 2113#define RGND_INTP_SHORT 3114 115/* HDMI registers */116#define HDMI_RX_TMDS0_CCTRL1_REG 0x10117#define HDMI_RX_TMDS_CLK_EN_REG 0x11118#define HDMI_RX_TMDS_CH_EN_REG 0x12119#define HDMI_RX_PLL_CALREFSEL_REG 0x17120#define HDMI_RX_PLL_VCOCAL_REG 0x1A121#define HDMI_RX_EQ_DATA0_REG 0x22122#define HDMI_RX_EQ_DATA1_REG 0x23123#define HDMI_RX_EQ_DATA2_REG 0x24124#define HDMI_RX_EQ_DATA3_REG 0x25125#define HDMI_RX_EQ_DATA4_REG 0x26126#define HDMI_RX_TMDS_ZONE_CTRL_REG 0x4C127#define HDMI_RX_TMDS_MODE_CTRL_REG 0x4D128 129/* CBUS registers */130#define CBUS_INT_STATUS_1_REG 0x08131#define CBUS_INTR1_ENABLE_REG 0x09132#define CBUS_MSC_REQ_ABORT_REASON_REG 0x0D133#define CBUS_INT_STATUS_2_REG 0x1E134#define CBUS_INTR2_ENABLE_REG 0x1F135#define CBUS_LINK_CONTROL_2_REG 0x31136#define CBUS_MHL_STATUS_REG_0 0xB0137#define CBUS_MHL_STATUS_REG_1 0xB1138 139#define BIT_CBUS_RESET BIT(3)140#define SET_HPD_DOWNSTREAM BIT(6)141 142/* TPI registers */143#define TPI_DPD_REG 0x3D144 145/* Timeouts in msec */146#define T_SRC_VBUS_CBUS_TO_STABLE 200147#define T_SRC_CBUS_FLOAT 100148#define T_SRC_CBUS_DEGLITCH 2149#define T_SRC_RXSENSE_DEGLITCH 110150 151#define MHL1_MAX_CLK 75000 /* in kHz */152 153#define I2C_TPI_ADDR 0x3D154#define I2C_HDMI_ADDR 0x49155#define I2C_CBUS_ADDR 0x64156 157enum sii9234_state {158 ST_OFF,159 ST_D3,160 ST_RGND_INIT,161 ST_RGND_1K,162 ST_RSEN_HIGH,163 ST_MHL_ESTABLISHED,164 ST_FAILURE_DISCOVERY,165 ST_FAILURE,166};167 168struct sii9234 {169 struct i2c_client *client[4];170 struct drm_bridge bridge;171 struct device *dev;172 struct gpio_desc *gpio_reset;173 int i2c_error;174 struct regulator_bulk_data supplies[4];175 176 struct mutex lock; /* Protects fields below and device registers */177 enum sii9234_state state;178};179 180enum sii9234_client_id {181 I2C_MHL,182 I2C_TPI,183 I2C_HDMI,184 I2C_CBUS,185};186 187static const char * const sii9234_client_name[] = {188 [I2C_MHL] = "MHL",189 [I2C_TPI] = "TPI",190 [I2C_HDMI] = "HDMI",191 [I2C_CBUS] = "CBUS",192};193 194static int sii9234_writeb(struct sii9234 *ctx, int id, int offset,195 int value)196{197 int ret;198 struct i2c_client *client = ctx->client[id];199 200 if (ctx->i2c_error)201 return ctx->i2c_error;202 203 ret = i2c_smbus_write_byte_data(client, offset, value);204 if (ret < 0)205 dev_err(ctx->dev, "writeb: %4s[0x%02x] <- 0x%02x\n",206 sii9234_client_name[id], offset, value);207 ctx->i2c_error = ret;208 209 return ret;210}211 212static int sii9234_writebm(struct sii9234 *ctx, int id, int offset,213 int value, int mask)214{215 int ret;216 struct i2c_client *client = ctx->client[id];217 218 if (ctx->i2c_error)219 return ctx->i2c_error;220 221 ret = i2c_smbus_write_byte(client, offset);222 if (ret < 0) {223 dev_err(ctx->dev, "writebm: %4s[0x%02x] <- 0x%02x\n",224 sii9234_client_name[id], offset, value);225 ctx->i2c_error = ret;226 return ret;227 }228 229 ret = i2c_smbus_read_byte(client);230 if (ret < 0) {231 dev_err(ctx->dev, "writebm: %4s[0x%02x] <- 0x%02x\n",232 sii9234_client_name[id], offset, value);233 ctx->i2c_error = ret;234 return ret;235 }236 237 value = (value & mask) | (ret & ~mask);238 239 ret = i2c_smbus_write_byte_data(client, offset, value);240 if (ret < 0) {241 dev_err(ctx->dev, "writebm: %4s[0x%02x] <- 0x%02x\n",242 sii9234_client_name[id], offset, value);243 ctx->i2c_error = ret;244 }245 246 return ret;247}248 249static int sii9234_readb(struct sii9234 *ctx, int id, int offset)250{251 int ret;252 struct i2c_client *client = ctx->client[id];253 254 if (ctx->i2c_error)255 return ctx->i2c_error;256 257 ret = i2c_smbus_write_byte(client, offset);258 if (ret < 0) {259 dev_err(ctx->dev, "readb: %4s[0x%02x]\n",260 sii9234_client_name[id], offset);261 ctx->i2c_error = ret;262 return ret;263 }264 265 ret = i2c_smbus_read_byte(client);266 if (ret < 0) {267 dev_err(ctx->dev, "readb: %4s[0x%02x]\n",268 sii9234_client_name[id], offset);269 ctx->i2c_error = ret;270 }271 272 return ret;273}274 275static int sii9234_clear_error(struct sii9234 *ctx)276{277 int ret = ctx->i2c_error;278 279 ctx->i2c_error = 0;280 281 return ret;282}283 284#define mhl_tx_writeb(sii9234, offset, value) \285 sii9234_writeb(sii9234, I2C_MHL, offset, value)286#define mhl_tx_writebm(sii9234, offset, value, mask) \287 sii9234_writebm(sii9234, I2C_MHL, offset, value, mask)288#define mhl_tx_readb(sii9234, offset) \289 sii9234_readb(sii9234, I2C_MHL, offset)290#define cbus_writeb(sii9234, offset, value) \291 sii9234_writeb(sii9234, I2C_CBUS, offset, value)292#define cbus_writebm(sii9234, offset, value, mask) \293 sii9234_writebm(sii9234, I2C_CBUS, offset, value, mask)294#define cbus_readb(sii9234, offset) \295 sii9234_readb(sii9234, I2C_CBUS, offset)296#define hdmi_writeb(sii9234, offset, value) \297 sii9234_writeb(sii9234, I2C_HDMI, offset, value)298#define hdmi_writebm(sii9234, offset, value, mask) \299 sii9234_writebm(sii9234, I2C_HDMI, offset, value, mask)300#define hdmi_readb(sii9234, offset) \301 sii9234_readb(sii9234, I2C_HDMI, offset)302#define tpi_writeb(sii9234, offset, value) \303 sii9234_writeb(sii9234, I2C_TPI, offset, value)304#define tpi_writebm(sii9234, offset, value, mask) \305 sii9234_writebm(sii9234, I2C_TPI, offset, value, mask)306#define tpi_readb(sii9234, offset) \307 sii9234_readb(sii9234, I2C_TPI, offset)308 309static u8 sii9234_tmds_control(struct sii9234 *ctx, bool enable)310{311 mhl_tx_writebm(ctx, MHL_TX_TMDS_CCTRL, enable ? ~0 : 0,312 BIT_TMDS_CCTRL_TMDS_OE);313 mhl_tx_writebm(ctx, MHL_TX_INT_CTRL_REG, enable ? ~0 : 0,314 MHL_HPD_OUT_OVR_EN | MHL_HPD_OUT_OVR_VAL);315 return sii9234_clear_error(ctx);316}317 318static int sii9234_cbus_reset(struct sii9234 *ctx)319{320 int i;321 322 mhl_tx_writebm(ctx, MHL_TX_SRST, ~0, BIT_CBUS_RESET);323 msleep(T_SRC_CBUS_DEGLITCH);324 mhl_tx_writebm(ctx, MHL_TX_SRST, 0, BIT_CBUS_RESET);325 326 for (i = 0; i < 4; i++) {327 /*328 * Enable WRITE_STAT interrupt for writes to all329 * 4 MSC Status registers.330 */331 cbus_writeb(ctx, 0xE0 + i, 0xF2);332 /*333 * Enable SET_INT interrupt for writes to all334 * 4 MSC Interrupt registers.335 */336 cbus_writeb(ctx, 0xF0 + i, 0xF2);337 }338 339 return sii9234_clear_error(ctx);340}341 342/* Require to chek mhl imformation of samsung in cbus_init_register */343static int sii9234_cbus_init(struct sii9234 *ctx)344{345 cbus_writeb(ctx, 0x07, 0xF2);346 cbus_writeb(ctx, 0x40, 0x03);347 cbus_writeb(ctx, 0x42, 0x06);348 cbus_writeb(ctx, 0x36, 0x0C);349 cbus_writeb(ctx, 0x3D, 0xFD);350 cbus_writeb(ctx, 0x1C, 0x01);351 cbus_writeb(ctx, 0x1D, 0x0F);352 cbus_writeb(ctx, 0x44, 0x02);353 /* Setup our devcap */354 cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_DEV_STATE, 0x00);355 cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_MHL_VERSION,356 SII9234_MHL_VERSION);357 cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_CAT,358 MHL_DCAP_CAT_SOURCE);359 cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_ADOPTER_ID_H, 0x01);360 cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_ADOPTER_ID_L, 0x41);361 cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_VID_LINK_MODE,362 MHL_DCAP_VID_LINK_RGB444 | MHL_DCAP_VID_LINK_YCBCR444);363 cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_VIDEO_TYPE,364 MHL_DCAP_VT_GRAPHICS);365 cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_LOG_DEV_MAP,366 MHL_DCAP_LD_GUI);367 cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_BANDWIDTH, 0x0F);368 cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_FEATURE_FLAG,369 MHL_DCAP_FEATURE_RCP_SUPPORT | MHL_DCAP_FEATURE_RAP_SUPPORT370 | MHL_DCAP_FEATURE_SP_SUPPORT);371 cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_DEVICE_ID_H, 0x0);372 cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_DEVICE_ID_L, 0x0);373 cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_SCRATCHPAD_SIZE,374 SII9234_SCRATCHPAD_SIZE);375 cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_INT_STAT_SIZE,376 SII9234_INT_STAT_SIZE);377 cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_RESERVED, 0);378 cbus_writebm(ctx, 0x31, 0x0C, 0x0C);379 cbus_writeb(ctx, 0x30, 0x01);380 cbus_writebm(ctx, 0x3C, 0x30, 0x38);381 cbus_writebm(ctx, 0x22, 0x0D, 0x0F);382 cbus_writebm(ctx, 0x2E, 0x15, 0x15);383 cbus_writeb(ctx, CBUS_INTR1_ENABLE_REG, 0);384 cbus_writeb(ctx, CBUS_INTR2_ENABLE_REG, 0);385 386 return sii9234_clear_error(ctx);387}388 389static void force_usb_id_switch_open(struct sii9234 *ctx)390{391 /* Disable CBUS discovery */392 mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL1_REG, 0, 0x01);393 /* Force USB ID switch to open */394 mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL6_REG, ~0, USB_ID_OVR);395 mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL3_REG, ~0, 0x86);396 /* Force upstream HPD to 0 when not in MHL mode. */397 mhl_tx_writebm(ctx, MHL_TX_INT_CTRL_REG, 0, 0x30);398}399 400static void release_usb_id_switch_open(struct sii9234 *ctx)401{402 msleep(T_SRC_CBUS_FLOAT);403 /* Clear USB ID switch to open */404 mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL6_REG, 0, USB_ID_OVR);405 /* Enable CBUS discovery */406 mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL1_REG, ~0, 0x01);407}408 409static int sii9234_power_init(struct sii9234 *ctx)410{411 /* Force the SiI9234 into the D0 state. */412 tpi_writeb(ctx, TPI_DPD_REG, 0x3F);413 /* Enable TxPLL Clock */414 hdmi_writeb(ctx, HDMI_RX_TMDS_CLK_EN_REG, 0x01);415 /* Enable Tx Clock Path & Equalizer */416 hdmi_writeb(ctx, HDMI_RX_TMDS_CH_EN_REG, 0x15);417 /* Power Up TMDS */418 mhl_tx_writeb(ctx, 0x08, 0x35);419 return sii9234_clear_error(ctx);420}421 422static int sii9234_hdmi_init(struct sii9234 *ctx)423{424 hdmi_writeb(ctx, HDMI_RX_TMDS0_CCTRL1_REG, 0xC1);425 hdmi_writeb(ctx, HDMI_RX_PLL_CALREFSEL_REG, 0x03);426 hdmi_writeb(ctx, HDMI_RX_PLL_VCOCAL_REG, 0x20);427 hdmi_writeb(ctx, HDMI_RX_EQ_DATA0_REG, 0x8A);428 hdmi_writeb(ctx, HDMI_RX_EQ_DATA1_REG, 0x6A);429 hdmi_writeb(ctx, HDMI_RX_EQ_DATA2_REG, 0xAA);430 hdmi_writeb(ctx, HDMI_RX_EQ_DATA3_REG, 0xCA);431 hdmi_writeb(ctx, HDMI_RX_EQ_DATA4_REG, 0xEA);432 hdmi_writeb(ctx, HDMI_RX_TMDS_ZONE_CTRL_REG, 0xA0);433 hdmi_writeb(ctx, HDMI_RX_TMDS_MODE_CTRL_REG, 0x00);434 mhl_tx_writeb(ctx, MHL_TX_TMDS_CCTRL, 0x34);435 hdmi_writeb(ctx, 0x45, 0x44);436 hdmi_writeb(ctx, 0x31, 0x0A);437 hdmi_writeb(ctx, HDMI_RX_TMDS0_CCTRL1_REG, 0xC1);438 439 return sii9234_clear_error(ctx);440}441 442static int sii9234_mhl_tx_ctl_int(struct sii9234 *ctx)443{444 mhl_tx_writeb(ctx, MHL_TX_MHLTX_CTL1_REG, 0xD0);445 mhl_tx_writeb(ctx, MHL_TX_MHLTX_CTL2_REG, 0xFC);446 mhl_tx_writeb(ctx, MHL_TX_MHLTX_CTL4_REG, 0xEB);447 mhl_tx_writeb(ctx, MHL_TX_MHLTX_CTL7_REG, 0x0C);448 449 return sii9234_clear_error(ctx);450}451 452static int sii9234_reset(struct sii9234 *ctx)453{454 int ret;455 456 sii9234_clear_error(ctx);457 458 ret = sii9234_power_init(ctx);459 if (ret < 0)460 return ret;461 ret = sii9234_cbus_reset(ctx);462 if (ret < 0)463 return ret;464 ret = sii9234_hdmi_init(ctx);465 if (ret < 0)466 return ret;467 ret = sii9234_mhl_tx_ctl_int(ctx);468 if (ret < 0)469 return ret;470 471 /* Enable HDCP Compliance safety */472 mhl_tx_writeb(ctx, 0x2B, 0x01);473 /* CBUS discovery cycle time for each drive and float = 150us */474 mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL1_REG, 0x04, 0x06);475 /* Clear bit 6 (reg_skip_rgnd) */476 mhl_tx_writeb(ctx, MHL_TX_DISC_CTRL2_REG, (1 << 7) /* Reserved */477 | 2 << ATT_THRESH_SHIFT | DEGLITCH_TIME_50MS);478 /*479 * Changed from 66 to 65 for 94[1:0] = 01 = 5k reg_cbusmhl_pup_sel480 * 1.8V CBUS VTH & GND threshold481 * to meet CTS 3.3.7.2 spec482 */483 mhl_tx_writeb(ctx, MHL_TX_DISC_CTRL5_REG, 0x77);484 cbus_writebm(ctx, CBUS_LINK_CONTROL_2_REG, ~0, MHL_INIT_TIMEOUT);485 mhl_tx_writeb(ctx, MHL_TX_MHLTX_CTL6_REG, 0xA0);486 /* RGND & single discovery attempt (RGND blocking) */487 mhl_tx_writeb(ctx, MHL_TX_DISC_CTRL6_REG, BLOCK_RGND_INT |488 DVRFLT_SEL | SINGLE_ATT);489 /* Use VBUS path of discovery state machine */490 mhl_tx_writeb(ctx, MHL_TX_DISC_CTRL8_REG, 0);491 /* 0x92[3] sets the CBUS / ID switch */492 mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL6_REG, ~0, USB_ID_OVR);493 /*494 * To allow RGND engine to operate correctly.495 * When moving the chip from D2 to D0 (power up, init regs)496 * the values should be497 * 94[1:0] = 01 reg_cbusmhl_pup_sel[1:0] should be set for 5k498 * 93[7:6] = 10 reg_cbusdisc_pup_sel[1:0] should be499 * set for 10k (default)500 * 93[5:4] = 00 reg_cbusidle_pup_sel[1:0] = open (default)501 */502 mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL3_REG, ~0, 0x86);503 /*504 * Change from CC to 8C to match 5K505 * to meet CTS 3.3.72 spec506 */507 mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL4_REG, ~0, 0x8C);508 /* Configure the interrupt as active high */509 mhl_tx_writebm(ctx, MHL_TX_INT_CTRL_REG, 0, 0x06);510 511 msleep(25);512 513 /* Release usb_id switch */514 mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL6_REG, 0, USB_ID_OVR);515 mhl_tx_writeb(ctx, MHL_TX_DISC_CTRL1_REG, 0x27);516 517 ret = sii9234_clear_error(ctx);518 if (ret < 0)519 return ret;520 ret = sii9234_cbus_init(ctx);521 if (ret < 0)522 return ret;523 524 /* Enable Auto soft reset on SCDT = 0 */525 mhl_tx_writeb(ctx, 0x05, 0x04);526 /* HDMI Transcode mode enable */527 mhl_tx_writeb(ctx, 0x0D, 0x1C);528 mhl_tx_writeb(ctx, MHL_TX_INTR4_ENABLE_REG,529 RGND_READY_MASK | CBUS_LKOUT_MASK530 | MHL_DISC_FAIL_MASK | MHL_EST_MASK);531 mhl_tx_writeb(ctx, MHL_TX_INTR1_ENABLE_REG, 0x60);532 533 /* This point is very important before measure RGND impedance */534 force_usb_id_switch_open(ctx);535 mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL4_REG, 0, 0xF0);536 mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL5_REG, 0, 0x03);537 release_usb_id_switch_open(ctx);538 539 /* Force upstream HPD to 0 when not in MHL mode */540 mhl_tx_writebm(ctx, MHL_TX_INT_CTRL_REG, 0, 1 << 5);541 mhl_tx_writebm(ctx, MHL_TX_INT_CTRL_REG, ~0, 1 << 4);542 543 return sii9234_clear_error(ctx);544}545 546static int sii9234_goto_d3(struct sii9234 *ctx)547{548 int ret;549 550 dev_dbg(ctx->dev, "sii9234: detection started d3\n");551 552 ret = sii9234_reset(ctx);553 if (ret < 0)554 goto exit;555 556 hdmi_writeb(ctx, 0x01, 0x03);557 tpi_writebm(ctx, TPI_DPD_REG, 0, 1);558 /* I2C above is expected to fail because power goes down */559 sii9234_clear_error(ctx);560 561 ctx->state = ST_D3;562 563 return 0;564 exit:565 dev_err(ctx->dev, "%s failed\n", __func__);566 return -1;567}568 569static int sii9234_hw_on(struct sii9234 *ctx)570{571 return regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies);572}573 574static void sii9234_hw_off(struct sii9234 *ctx)575{576 gpiod_set_value(ctx->gpio_reset, 1);577 msleep(20);578 regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);579}580 581static void sii9234_hw_reset(struct sii9234 *ctx)582{583 gpiod_set_value(ctx->gpio_reset, 1);584 msleep(20);585 gpiod_set_value(ctx->gpio_reset, 0);586}587 588static void sii9234_cable_in(struct sii9234 *ctx)589{590 int ret;591 592 mutex_lock(&ctx->lock);593 if (ctx->state != ST_OFF)594 goto unlock;595 ret = sii9234_hw_on(ctx);596 if (ret < 0)597 goto unlock;598 599 sii9234_hw_reset(ctx);600 sii9234_goto_d3(ctx);601 /* To avoid irq storm, when hw is in meta state */602 enable_irq(to_i2c_client(ctx->dev)->irq);603 604unlock:605 mutex_unlock(&ctx->lock);606}607 608static void sii9234_cable_out(struct sii9234 *ctx)609{610 mutex_lock(&ctx->lock);611 612 if (ctx->state == ST_OFF)613 goto unlock;614 615 disable_irq(to_i2c_client(ctx->dev)->irq);616 tpi_writeb(ctx, TPI_DPD_REG, 0);617 /* Turn on&off hpd festure for only QCT HDMI */618 sii9234_hw_off(ctx);619 620 ctx->state = ST_OFF;621 622unlock:623 mutex_unlock(&ctx->lock);624}625 626static enum sii9234_state sii9234_rgnd_ready_irq(struct sii9234 *ctx)627{628 int value;629 630 if (ctx->state == ST_D3) {631 int ret;632 633 dev_dbg(ctx->dev, "RGND_READY_INT\n");634 sii9234_hw_reset(ctx);635 636 ret = sii9234_reset(ctx);637 if (ret < 0) {638 dev_err(ctx->dev, "sii9234_reset() failed\n");639 return ST_FAILURE;640 }641 642 return ST_RGND_INIT;643 }644 645 /* Got interrupt in inappropriate state */646 if (ctx->state != ST_RGND_INIT)647 return ST_FAILURE;648 649 value = mhl_tx_readb(ctx, MHL_TX_STAT2_REG);650 if (sii9234_clear_error(ctx))651 return ST_FAILURE;652 653 if ((value & RGND_INTP_MASK) != RGND_INTP_1K) {654 dev_warn(ctx->dev, "RGND is not 1k\n");655 return ST_RGND_INIT;656 }657 dev_dbg(ctx->dev, "RGND 1K!!\n");658 mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL4_REG, ~0, 0x8C);659 mhl_tx_writeb(ctx, MHL_TX_DISC_CTRL5_REG, 0x77);660 mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL6_REG, ~0, 0x05);661 if (sii9234_clear_error(ctx))662 return ST_FAILURE;663 664 msleep(T_SRC_VBUS_CBUS_TO_STABLE);665 return ST_RGND_1K;666}667 668static enum sii9234_state sii9234_mhl_established(struct sii9234 *ctx)669{670 dev_dbg(ctx->dev, "mhl est interrupt\n");671 672 /* Discovery override */673 mhl_tx_writeb(ctx, MHL_TX_MHLTX_CTL1_REG, 0x10);674 /* Increase DDC translation layer timer (byte mode) */675 cbus_writeb(ctx, 0x07, 0x32);676 cbus_writebm(ctx, 0x44, ~0, 1 << 1);677 /* Keep the discovery enabled. Need RGND interrupt */678 mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL1_REG, ~0, 1);679 mhl_tx_writeb(ctx, MHL_TX_INTR1_ENABLE_REG,680 RSEN_CHANGE_INT_MASK | HPD_CHANGE_INT_MASK);681 682 if (sii9234_clear_error(ctx))683 return ST_FAILURE;684 685 return ST_MHL_ESTABLISHED;686}687 688static enum sii9234_state sii9234_hpd_change(struct sii9234 *ctx)689{690 int value;691 692 value = cbus_readb(ctx, CBUS_MSC_REQ_ABORT_REASON_REG);693 if (sii9234_clear_error(ctx))694 return ST_FAILURE;695 696 if (value & SET_HPD_DOWNSTREAM) {697 /* Downstream HPD High, Enable TMDS */698 sii9234_tmds_control(ctx, true);699 } else {700 /* Downstream HPD Low, Disable TMDS */701 sii9234_tmds_control(ctx, false);702 }703 704 return ctx->state;705}706 707static enum sii9234_state sii9234_rsen_change(struct sii9234 *ctx)708{709 int value;710 711 /* Work_around code to handle wrong interrupt */712 if (ctx->state != ST_RGND_1K) {713 dev_err(ctx->dev, "RSEN_HIGH without RGND_1K\n");714 return ST_FAILURE;715 }716 value = mhl_tx_readb(ctx, MHL_TX_SYSSTAT_REG);717 if (value < 0)718 return ST_FAILURE;719 720 if (value & RSEN_STATUS) {721 dev_dbg(ctx->dev, "MHL cable connected.. RSEN High\n");722 return ST_RSEN_HIGH;723 }724 dev_dbg(ctx->dev, "RSEN lost\n");725 /*726 * Once RSEN loss is confirmed,we need to check727 * based on cable status and chip power status,whether728 * it is SINK Loss(HDMI cable not connected, TV Off)729 * or MHL cable disconnection730 * TODO: Define the below mhl_disconnection()731 */732 msleep(T_SRC_RXSENSE_DEGLITCH);733 value = mhl_tx_readb(ctx, MHL_TX_SYSSTAT_REG);734 if (value < 0)735 return ST_FAILURE;736 dev_dbg(ctx->dev, "sys_stat: %x\n", value);737 738 if (value & RSEN_STATUS) {739 dev_dbg(ctx->dev, "RSEN recovery\n");740 return ST_RSEN_HIGH;741 }742 dev_dbg(ctx->dev, "RSEN Really LOW\n");743 /* To meet CTS 3.3.22.2 spec */744 sii9234_tmds_control(ctx, false);745 force_usb_id_switch_open(ctx);746 release_usb_id_switch_open(ctx);747 748 return ST_FAILURE;749}750 751static irqreturn_t sii9234_irq_thread(int irq, void *data)752{753 struct sii9234 *ctx = data;754 int intr1, intr4;755 int intr1_en, intr4_en;756 int cbus_intr1, cbus_intr2;757 758 dev_dbg(ctx->dev, "%s\n", __func__);759 760 mutex_lock(&ctx->lock);761 762 intr1 = mhl_tx_readb(ctx, MHL_TX_INTR1_REG);763 intr4 = mhl_tx_readb(ctx, MHL_TX_INTR4_REG);764 intr1_en = mhl_tx_readb(ctx, MHL_TX_INTR1_ENABLE_REG);765 intr4_en = mhl_tx_readb(ctx, MHL_TX_INTR4_ENABLE_REG);766 cbus_intr1 = cbus_readb(ctx, CBUS_INT_STATUS_1_REG);767 cbus_intr2 = cbus_readb(ctx, CBUS_INT_STATUS_2_REG);768 769 if (sii9234_clear_error(ctx))770 goto done;771 772 dev_dbg(ctx->dev, "irq %02x/%02x %02x/%02x %02x/%02x\n",773 intr1, intr1_en, intr4, intr4_en, cbus_intr1, cbus_intr2);774 775 if (intr4 & RGND_READY_INT)776 ctx->state = sii9234_rgnd_ready_irq(ctx);777 if (intr1 & RSEN_CHANGE_INT)778 ctx->state = sii9234_rsen_change(ctx);779 if (intr4 & MHL_EST_INT)780 ctx->state = sii9234_mhl_established(ctx);781 if (intr1 & HPD_CHANGE_INT)782 ctx->state = sii9234_hpd_change(ctx);783 if (intr4 & CBUS_LKOUT_INT)784 ctx->state = ST_FAILURE;785 if (intr4 & MHL_DISC_FAIL_INT)786 ctx->state = ST_FAILURE_DISCOVERY;787 788 done:789 /* Clean interrupt status and pending flags */790 mhl_tx_writeb(ctx, MHL_TX_INTR1_REG, intr1);791 mhl_tx_writeb(ctx, MHL_TX_INTR4_REG, intr4);792 cbus_writeb(ctx, CBUS_MHL_STATUS_REG_0, 0xFF);793 cbus_writeb(ctx, CBUS_MHL_STATUS_REG_1, 0xFF);794 cbus_writeb(ctx, CBUS_INT_STATUS_1_REG, cbus_intr1);795 cbus_writeb(ctx, CBUS_INT_STATUS_2_REG, cbus_intr2);796 797 sii9234_clear_error(ctx);798 799 if (ctx->state == ST_FAILURE) {800 dev_dbg(ctx->dev, "try to reset after failure\n");801 sii9234_hw_reset(ctx);802 sii9234_goto_d3(ctx);803 }804 805 if (ctx->state == ST_FAILURE_DISCOVERY) {806 dev_err(ctx->dev, "discovery failed, no power for MHL?\n");807 tpi_writebm(ctx, TPI_DPD_REG, 0, 1);808 ctx->state = ST_D3;809 }810 811 mutex_unlock(&ctx->lock);812 813 return IRQ_HANDLED;814}815 816static int sii9234_init_resources(struct sii9234 *ctx,817 struct i2c_client *client)818{819 struct i2c_adapter *adapter = client->adapter;820 int ret;821 822 if (!ctx->dev->of_node) {823 dev_err(ctx->dev, "not DT device\n");824 return -ENODEV;825 }826 827 ctx->gpio_reset = devm_gpiod_get(ctx->dev, "reset", GPIOD_OUT_LOW);828 if (IS_ERR(ctx->gpio_reset)) {829 dev_err(ctx->dev, "failed to get reset gpio from DT\n");830 return PTR_ERR(ctx->gpio_reset);831 }832 833 ctx->supplies[0].supply = "avcc12";834 ctx->supplies[1].supply = "avcc33";835 ctx->supplies[2].supply = "iovcc18";836 ctx->supplies[3].supply = "cvcc12";837 ret = devm_regulator_bulk_get(ctx->dev, 4, ctx->supplies);838 if (ret) {839 if (ret != -EPROBE_DEFER)840 dev_err(ctx->dev, "regulator_bulk failed\n");841 return ret;842 }843 844 ctx->client[I2C_MHL] = client;845 846 ctx->client[I2C_TPI] = devm_i2c_new_dummy_device(&client->dev, adapter,847 I2C_TPI_ADDR);848 if (IS_ERR(ctx->client[I2C_TPI])) {849 dev_err(ctx->dev, "failed to create TPI client\n");850 return PTR_ERR(ctx->client[I2C_TPI]);851 }852 853 ctx->client[I2C_HDMI] = devm_i2c_new_dummy_device(&client->dev, adapter,854 I2C_HDMI_ADDR);855 if (IS_ERR(ctx->client[I2C_HDMI])) {856 dev_err(ctx->dev, "failed to create HDMI RX client\n");857 return PTR_ERR(ctx->client[I2C_HDMI]);858 }859 860 ctx->client[I2C_CBUS] = devm_i2c_new_dummy_device(&client->dev, adapter,861 I2C_CBUS_ADDR);862 if (IS_ERR(ctx->client[I2C_CBUS])) {863 dev_err(ctx->dev, "failed to create CBUS client\n");864 return PTR_ERR(ctx->client[I2C_CBUS]);865 }866 867 return 0;868}869 870static enum drm_mode_status sii9234_mode_valid(struct drm_bridge *bridge,871 const struct drm_display_info *info,872 const struct drm_display_mode *mode)873{874 if (mode->clock > MHL1_MAX_CLK)875 return MODE_CLOCK_HIGH;876 877 return MODE_OK;878}879 880static const struct drm_bridge_funcs sii9234_bridge_funcs = {881 .mode_valid = sii9234_mode_valid,882};883 884static int sii9234_probe(struct i2c_client *client)885{886 struct i2c_adapter *adapter = client->adapter;887 struct sii9234 *ctx;888 struct device *dev = &client->dev;889 int ret;890 891 ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);892 if (!ctx)893 return -ENOMEM;894 895 ctx->dev = dev;896 mutex_init(&ctx->lock);897 898 if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {899 dev_err(dev, "I2C adapter lacks SMBUS feature\n");900 return -EIO;901 }902 903 if (!client->irq) {904 dev_err(dev, "no irq provided\n");905 return -EINVAL;906 }907 908 irq_set_status_flags(client->irq, IRQ_NOAUTOEN);909 ret = devm_request_threaded_irq(dev, client->irq, NULL,910 sii9234_irq_thread,911 IRQF_TRIGGER_HIGH | IRQF_ONESHOT,912 "sii9234", ctx);913 if (ret < 0) {914 dev_err(dev, "failed to install IRQ handler\n");915 return ret;916 }917 918 ret = sii9234_init_resources(ctx, client);919 if (ret < 0)920 return ret;921 922 i2c_set_clientdata(client, ctx);923 924 ctx->bridge.funcs = &sii9234_bridge_funcs;925 ctx->bridge.of_node = dev->of_node;926 drm_bridge_add(&ctx->bridge);927 928 sii9234_cable_in(ctx);929 930 return 0;931}932 933static void sii9234_remove(struct i2c_client *client)934{935 struct sii9234 *ctx = i2c_get_clientdata(client);936 937 sii9234_cable_out(ctx);938 drm_bridge_remove(&ctx->bridge);939}940 941static const struct of_device_id sii9234_dt_match[] = {942 { .compatible = "sil,sii9234" },943 { },944};945MODULE_DEVICE_TABLE(of, sii9234_dt_match);946 947static const struct i2c_device_id sii9234_id[] = {948 { "SII9234", 0 },949 { },950};951MODULE_DEVICE_TABLE(i2c, sii9234_id);952 953static struct i2c_driver sii9234_driver = {954 .driver = {955 .name = "sii9234",956 .of_match_table = sii9234_dt_match,957 },958 .probe = sii9234_probe,959 .remove = sii9234_remove,960 .id_table = sii9234_id,961};962 963module_i2c_driver(sii9234_driver);964MODULE_DESCRIPTION("Silicon Image SII9234 HDMI/MHL bridge driver");965MODULE_LICENSE("GPL");966