summaryrefslogtreecommitdiff
path: root/drivers/gpu/drm/bridge
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu/drm/bridge')
-rw-r--r--drivers/gpu/drm/bridge/Kconfig10
-rw-r--r--drivers/gpu/drm/bridge/Makefile1
-rw-r--r--drivers/gpu/drm/bridge/adv7511/Kconfig8
-rw-r--r--drivers/gpu/drm/bridge/adv7511/Makefile1
-rw-r--r--drivers/gpu/drm/bridge/adv7511/adv7511.h43
-rw-r--r--drivers/gpu/drm/bridge/adv7511/adv7511_cec.c337
-rw-r--r--drivers/gpu/drm/bridge/adv7511/adv7511_drv.c116
-rw-r--r--drivers/gpu/drm/bridge/adv7511/adv7533.c38
-rw-r--r--drivers/gpu/drm/bridge/panel.c10
-rw-r--r--drivers/gpu/drm/bridge/sii9234.c994
-rw-r--r--drivers/gpu/drm/bridge/sil-sii8620.c96
-rw-r--r--drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c5
12 files changed, 1590 insertions, 69 deletions
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index adf9ae0e0b7c..3b99d5a06c16 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -71,7 +71,7 @@ config DRM_PARADE_PS8622
config DRM_SIL_SII8620
tristate "Silicon Image SII8620 HDMI/MHL bridge"
- depends on OF
+ depends on OF && RC_CORE
select DRM_KMS_HELPER
help
Silicon Image SII8620 HDMI/MHL bridge chip driver.
@@ -84,6 +84,14 @@ config DRM_SII902X
---help---
Silicon Image sii902x bridge chip driver.
+config DRM_SII9234
+ tristate "Silicon Image SII9234 HDMI/MHL bridge"
+ depends on OF
+ ---help---
+ Say Y here if you want support for the MHL interface.
+ It is an I2C driver, that detects connection of MHL bridge
+ and starts encapsulation of HDMI signal.
+
config DRM_TOSHIBA_TC358767
tristate "Toshiba TC358767 eDP bridge"
depends on OF
diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
index defcf1e7ca1c..e3d5eb031f18 100644
--- a/drivers/gpu/drm/bridge/Makefile
+++ b/drivers/gpu/drm/bridge/Makefile
@@ -6,6 +6,7 @@ obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o
obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o
obj-$(CONFIG_DRM_SIL_SII8620) += sil-sii8620.o
obj-$(CONFIG_DRM_SII902X) += sii902x.o
+obj-$(CONFIG_DRM_SII9234) += sii9234.o
obj-$(CONFIG_DRM_TOSHIBA_TC358767) += tc358767.o
obj-$(CONFIG_DRM_ANALOGIX_DP) += analogix/
obj-$(CONFIG_DRM_I2C_ADV7511) += adv7511/
diff --git a/drivers/gpu/drm/bridge/adv7511/Kconfig b/drivers/gpu/drm/bridge/adv7511/Kconfig
index 2fed567f9943..592b9d2ec034 100644
--- a/drivers/gpu/drm/bridge/adv7511/Kconfig
+++ b/drivers/gpu/drm/bridge/adv7511/Kconfig
@@ -21,3 +21,11 @@ config DRM_I2C_ADV7533
default y
help
Support for the Analog Devices ADV7533 DSI to HDMI encoder.
+
+config DRM_I2C_ADV7511_CEC
+ bool "ADV7511/33 HDMI CEC driver"
+ depends on DRM_I2C_ADV7511
+ select CEC_CORE
+ default y
+ help
+ When selected the HDMI transmitter will support the CEC feature.
diff --git a/drivers/gpu/drm/bridge/adv7511/Makefile b/drivers/gpu/drm/bridge/adv7511/Makefile
index 5ba675534f6e..5bb384938a71 100644
--- a/drivers/gpu/drm/bridge/adv7511/Makefile
+++ b/drivers/gpu/drm/bridge/adv7511/Makefile
@@ -1,4 +1,5 @@
adv7511-y := adv7511_drv.o
adv7511-$(CONFIG_DRM_I2C_ADV7511_AUDIO) += adv7511_audio.o
+adv7511-$(CONFIG_DRM_I2C_ADV7511_CEC) += adv7511_cec.o
adv7511-$(CONFIG_DRM_I2C_ADV7533) += adv7533.o
obj-$(CONFIG_DRM_I2C_ADV7511) += adv7511.o
diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511.h b/drivers/gpu/drm/bridge/adv7511/adv7511.h
index 12ef2d8ee110..b4efcbabf7f7 100644
--- a/drivers/gpu/drm/bridge/adv7511/adv7511.h
+++ b/drivers/gpu/drm/bridge/adv7511/adv7511.h
@@ -195,6 +195,25 @@
#define ADV7511_PACKET_GM(x) ADV7511_PACKET(5, x)
#define ADV7511_PACKET_SPARE(x) ADV7511_PACKET(6, x)
+#define ADV7511_REG_CEC_TX_FRAME_HDR 0x00
+#define ADV7511_REG_CEC_TX_FRAME_DATA0 0x01
+#define ADV7511_REG_CEC_TX_FRAME_LEN 0x10
+#define ADV7511_REG_CEC_TX_ENABLE 0x11
+#define ADV7511_REG_CEC_TX_RETRY 0x12
+#define ADV7511_REG_CEC_TX_LOW_DRV_CNT 0x14
+#define ADV7511_REG_CEC_RX_FRAME_HDR 0x15
+#define ADV7511_REG_CEC_RX_FRAME_DATA0 0x16
+#define ADV7511_REG_CEC_RX_FRAME_LEN 0x25
+#define ADV7511_REG_CEC_RX_ENABLE 0x26
+#define ADV7511_REG_CEC_RX_BUFFERS 0x4a
+#define ADV7511_REG_CEC_LOG_ADDR_MASK 0x4b
+#define ADV7511_REG_CEC_LOG_ADDR_0_1 0x4c
+#define ADV7511_REG_CEC_LOG_ADDR_2 0x4d
+#define ADV7511_REG_CEC_CLK_DIV 0x4e
+#define ADV7511_REG_CEC_SOFT_RESET 0x50
+
+#define ADV7533_REG_CEC_OFFSET 0x70
+
enum adv7511_input_clock {
ADV7511_INPUT_CLOCK_1X,
ADV7511_INPUT_CLOCK_2X,
@@ -297,6 +316,8 @@ enum adv7511_type {
ADV7533,
};
+#define ADV7511_MAX_ADDRS 3
+
struct adv7511 {
struct i2c_client *i2c_main;
struct i2c_client *i2c_edid;
@@ -341,15 +362,27 @@ struct adv7511 {
enum adv7511_type type;
struct platform_device *audio_pdev;
+
+ struct cec_adapter *cec_adap;
+ u8 cec_addr[ADV7511_MAX_ADDRS];
+ u8 cec_valid_addrs;
+ bool cec_enabled_adap;
+ struct clk *cec_clk;
+ u32 cec_clk_freq;
};
+#ifdef CONFIG_DRM_I2C_ADV7511_CEC
+int adv7511_cec_init(struct device *dev, struct adv7511 *adv7511,
+ unsigned int offset);
+void adv7511_cec_irq_process(struct adv7511 *adv7511, unsigned int irq1);
+#endif
+
#ifdef CONFIG_DRM_I2C_ADV7533
void adv7533_dsi_power_on(struct adv7511 *adv);
void adv7533_dsi_power_off(struct adv7511 *adv);
void adv7533_mode_set(struct adv7511 *adv, struct drm_display_mode *mode);
int adv7533_patch_registers(struct adv7511 *adv);
-void adv7533_uninit_cec(struct adv7511 *adv);
-int adv7533_init_cec(struct adv7511 *adv);
+int adv7533_patch_cec_registers(struct adv7511 *adv);
int adv7533_attach_dsi(struct adv7511 *adv);
void adv7533_detach_dsi(struct adv7511 *adv);
int adv7533_parse_dt(struct device_node *np, struct adv7511 *adv);
@@ -372,11 +405,7 @@ static inline int adv7533_patch_registers(struct adv7511 *adv)
return -ENODEV;
}
-static inline void adv7533_uninit_cec(struct adv7511 *adv)
-{
-}
-
-static inline int adv7533_init_cec(struct adv7511 *adv)
+static inline int adv7533_patch_cec_registers(struct adv7511 *adv)
{
return -ENODEV;
}
diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511_cec.c b/drivers/gpu/drm/bridge/adv7511/adv7511_cec.c
new file mode 100644
index 000000000000..b33d730e4d73
--- /dev/null
+++ b/drivers/gpu/drm/bridge/adv7511/adv7511_cec.c
@@ -0,0 +1,337 @@
+/*
+ * adv7511_cec.c - Analog Devices ADV7511/33 cec driver
+ *
+ * Copyright 2017 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
+ *
+ * This program is free software; you may 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.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ */
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/slab.h>
+#include <linux/clk.h>
+
+#include <media/cec.h>
+
+#include "adv7511.h"
+
+#define ADV7511_INT1_CEC_MASK \
+ (ADV7511_INT1_CEC_TX_READY | ADV7511_INT1_CEC_TX_ARBIT_LOST | \
+ ADV7511_INT1_CEC_TX_RETRY_TIMEOUT | ADV7511_INT1_CEC_RX_READY1)
+
+static void adv_cec_tx_raw_status(struct adv7511 *adv7511, u8 tx_raw_status)
+{
+ unsigned int offset = adv7511->type == ADV7533 ?
+ ADV7533_REG_CEC_OFFSET : 0;
+ unsigned int val;
+
+ if (regmap_read(adv7511->regmap_cec,
+ ADV7511_REG_CEC_TX_ENABLE + offset, &val))
+ return;
+
+ if ((val & 0x01) == 0)
+ return;
+
+ if (tx_raw_status & ADV7511_INT1_CEC_TX_ARBIT_LOST) {
+ cec_transmit_attempt_done(adv7511->cec_adap,
+ CEC_TX_STATUS_ARB_LOST);
+ return;
+ }
+ if (tx_raw_status & ADV7511_INT1_CEC_TX_RETRY_TIMEOUT) {
+ u8 status;
+ u8 err_cnt = 0;
+ u8 nack_cnt = 0;
+ u8 low_drive_cnt = 0;
+ unsigned int cnt;
+
+ /*
+ * We set this status bit since this hardware performs
+ * retransmissions.
+ */
+ status = CEC_TX_STATUS_MAX_RETRIES;
+ if (regmap_read(adv7511->regmap_cec,
+ ADV7511_REG_CEC_TX_LOW_DRV_CNT + offset, &cnt)) {
+ err_cnt = 1;
+ status |= CEC_TX_STATUS_ERROR;
+ } else {
+ nack_cnt = cnt & 0xf;
+ if (nack_cnt)
+ status |= CEC_TX_STATUS_NACK;
+ low_drive_cnt = cnt >> 4;
+ if (low_drive_cnt)
+ status |= CEC_TX_STATUS_LOW_DRIVE;
+ }
+ cec_transmit_done(adv7511->cec_adap, status,
+ 0, nack_cnt, low_drive_cnt, err_cnt);
+ return;
+ }
+ if (tx_raw_status & ADV7511_INT1_CEC_TX_READY) {
+ cec_transmit_attempt_done(adv7511->cec_adap, CEC_TX_STATUS_OK);
+ return;
+ }
+}
+
+void adv7511_cec_irq_process(struct adv7511 *adv7511, unsigned int irq1)
+{
+ unsigned int offset = adv7511->type == ADV7533 ?
+ ADV7533_REG_CEC_OFFSET : 0;
+ const u32 irq_tx_mask = ADV7511_INT1_CEC_TX_READY |
+ ADV7511_INT1_CEC_TX_ARBIT_LOST |
+ ADV7511_INT1_CEC_TX_RETRY_TIMEOUT;
+ struct cec_msg msg = {};
+ unsigned int len;
+ unsigned int val;
+ u8 i;
+
+ if (irq1 & irq_tx_mask)
+ adv_cec_tx_raw_status(adv7511, irq1);
+
+ if (!(irq1 & ADV7511_INT1_CEC_RX_READY1))
+ return;
+
+ if (regmap_read(adv7511->regmap_cec,
+ ADV7511_REG_CEC_RX_FRAME_LEN + offset, &len))
+ return;
+
+ msg.len = len & 0x1f;
+
+ if (msg.len > 16)
+ msg.len = 16;
+
+ if (!msg.len)
+ return;
+
+ for (i = 0; i < msg.len; i++) {
+ regmap_read(adv7511->regmap_cec,
+ i + ADV7511_REG_CEC_RX_FRAME_HDR + offset, &val);
+ msg.msg[i] = val;
+ }
+
+ /* toggle to re-enable rx 1 */
+ regmap_write(adv7511->regmap_cec,
+ ADV7511_REG_CEC_RX_BUFFERS + offset, 1);
+ regmap_write(adv7511->regmap_cec,
+ ADV7511_REG_CEC_RX_BUFFERS + offset, 0);
+ cec_received_msg(adv7511->cec_adap, &msg);
+}
+
+static int adv7511_cec_adap_enable(struct cec_adapter *adap, bool enable)
+{
+ struct adv7511 *adv7511 = cec_get_drvdata(adap);
+ unsigned int offset = adv7511->type == ADV7533 ?
+ ADV7533_REG_CEC_OFFSET : 0;
+
+ if (adv7511->i2c_cec == NULL)
+ return -EIO;
+
+ if (!adv7511->cec_enabled_adap && enable) {
+ /* power up cec section */
+ regmap_update_bits(adv7511->regmap_cec,
+ ADV7511_REG_CEC_CLK_DIV + offset,
+ 0x03, 0x01);
+ /* legacy mode and clear all rx buffers */
+ regmap_write(adv7511->regmap_cec,
+ ADV7511_REG_CEC_RX_BUFFERS + offset, 0x07);
+ regmap_write(adv7511->regmap_cec,
+ ADV7511_REG_CEC_RX_BUFFERS + offset, 0);
+ /* initially disable tx */
+ regmap_update_bits(adv7511->regmap_cec,
+ ADV7511_REG_CEC_TX_ENABLE + offset, 1, 0);
+ /* enabled irqs: */
+ /* tx: ready */
+ /* tx: arbitration lost */
+ /* tx: retry timeout */
+ /* rx: ready 1 */
+ regmap_update_bits(adv7511->regmap,
+ ADV7511_REG_INT_ENABLE(1), 0x3f,
+ ADV7511_INT1_CEC_MASK);
+ } else if (adv7511->cec_enabled_adap && !enable) {
+ regmap_update_bits(adv7511->regmap,
+ ADV7511_REG_INT_ENABLE(1), 0x3f, 0);
+ /* disable address mask 1-3 */
+ regmap_update_bits(adv7511->regmap_cec,
+ ADV7511_REG_CEC_LOG_ADDR_MASK + offset,
+ 0x70, 0x00);
+ /* power down cec section */
+ regmap_update_bits(adv7511->regmap_cec,
+ ADV7511_REG_CEC_CLK_DIV + offset,
+ 0x03, 0x00);
+ adv7511->cec_valid_addrs = 0;
+ }
+ adv7511->cec_enabled_adap = enable;
+ return 0;
+}
+
+static int adv7511_cec_adap_log_addr(struct cec_adapter *adap, u8 addr)
+{
+ struct adv7511 *adv7511 = cec_get_drvdata(adap);
+ unsigned int offset = adv7511->type == ADV7533 ?
+ ADV7533_REG_CEC_OFFSET : 0;
+ unsigned int i, free_idx = ADV7511_MAX_ADDRS;
+
+ if (!adv7511->cec_enabled_adap)
+ return addr == CEC_LOG_ADDR_INVALID ? 0 : -EIO;
+
+ if (addr == CEC_LOG_ADDR_INVALID) {
+ regmap_update_bits(adv7511->regmap_cec,
+ ADV7511_REG_CEC_LOG_ADDR_MASK + offset,
+ 0x70, 0);
+ adv7511->cec_valid_addrs = 0;
+ return 0;
+ }
+
+ for (i = 0; i < ADV7511_MAX_ADDRS; i++) {
+ bool is_valid = adv7511->cec_valid_addrs & (1 << i);
+
+ if (free_idx == ADV7511_MAX_ADDRS && !is_valid)
+ free_idx = i;
+ if (is_valid && adv7511->cec_addr[i] == addr)
+ return 0;
+ }
+ if (i == ADV7511_MAX_ADDRS) {
+ i = free_idx;
+ if (i == ADV7511_MAX_ADDRS)
+ return -ENXIO;
+ }
+ adv7511->cec_addr[i] = addr;
+ adv7511->cec_valid_addrs |= 1 << i;
+
+ switch (i) {
+ case 0:
+ /* enable address mask 0 */
+ regmap_update_bits(adv7511->regmap_cec,
+ ADV7511_REG_CEC_LOG_ADDR_MASK + offset,
+ 0x10, 0x10);
+ /* set address for mask 0 */
+ regmap_update_bits(adv7511->regmap_cec,
+ ADV7511_REG_CEC_LOG_ADDR_0_1 + offset,
+ 0x0f, addr);
+ break;
+ case 1:
+ /* enable address mask 1 */
+ regmap_update_bits(adv7511->regmap_cec,
+ ADV7511_REG_CEC_LOG_ADDR_MASK + offset,
+ 0x20, 0x20);
+ /* set address for mask 1 */
+ regmap_update_bits(adv7511->regmap_cec,
+ ADV7511_REG_CEC_LOG_ADDR_0_1 + offset,
+ 0xf0, addr << 4);
+ break;
+ case 2:
+ /* enable address mask 2 */
+ regmap_update_bits(adv7511->regmap_cec,
+ ADV7511_REG_CEC_LOG_ADDR_MASK + offset,
+ 0x40, 0x40);
+ /* set address for mask 1 */
+ regmap_update_bits(adv7511->regmap_cec,
+ ADV7511_REG_CEC_LOG_ADDR_2 + offset,
+ 0x0f, addr);
+ break;
+ }
+ return 0;
+}
+
+static int adv7511_cec_adap_transmit(struct cec_adapter *adap, u8 attempts,
+ u32 signal_free_time, struct cec_msg *msg)
+{
+ struct adv7511 *adv7511 = cec_get_drvdata(adap);
+ unsigned int offset = adv7511->type == ADV7533 ?
+ ADV7533_REG_CEC_OFFSET : 0;
+ u8 len = msg->len;
+ unsigned int i;
+
+ /*
+ * The number of retries is the number of attempts - 1, but retry
+ * at least once. It's not clear if a value of 0 is allowed, so
+ * let's do at least one retry.
+ */
+ regmap_update_bits(adv7511->regmap_cec,
+ ADV7511_REG_CEC_TX_RETRY + offset,
+ 0x70, max(1, attempts - 1) << 4);
+
+ /* blocking, clear cec tx irq status */
+ regmap_update_bits(adv7511->regmap, ADV7511_REG_INT(1), 0x38, 0x38);
+
+ /* write data */
+ for (i = 0; i < len; i++)
+ regmap_write(adv7511->regmap_cec,
+ i + ADV7511_REG_CEC_TX_FRAME_HDR + offset,
+ msg->msg[i]);
+
+ /* set length (data + header) */
+ regmap_write(adv7511->regmap_cec,
+ ADV7511_REG_CEC_TX_FRAME_LEN + offset, len);
+ /* start transmit, enable tx */
+ regmap_write(adv7511->regmap_cec,
+ ADV7511_REG_CEC_TX_ENABLE + offset, 0x01);
+ return 0;
+}
+
+static const struct cec_adap_ops adv7511_cec_adap_ops = {
+ .adap_enable = adv7511_cec_adap_enable,
+ .adap_log_addr = adv7511_cec_adap_log_addr,
+ .adap_transmit = adv7511_cec_adap_transmit,
+};
+
+static int adv7511_cec_parse_dt(struct device *dev, struct adv7511 *adv7511)
+{
+ adv7511->cec_clk = devm_clk_get(dev, "cec");
+ if (IS_ERR(adv7511->cec_clk)) {
+ int ret = PTR_ERR(adv7511->cec_clk);
+
+ adv7511->cec_clk = NULL;
+ return ret;
+ }
+ clk_prepare_enable(adv7511->cec_clk);
+ adv7511->cec_clk_freq = clk_get_rate(adv7511->cec_clk);
+ return 0;
+}
+
+int adv7511_cec_init(struct device *dev, struct adv7511 *adv7511,
+ unsigned int offset)
+{
+ int ret = adv7511_cec_parse_dt(dev, adv7511);
+
+ if (ret)
+ return ret;
+
+ adv7511->cec_adap = cec_allocate_adapter(&adv7511_cec_adap_ops,
+ adv7511, dev_name(dev), CEC_CAP_DEFAULTS, ADV7511_MAX_ADDRS);
+ if (IS_ERR(adv7511->cec_adap))
+ return PTR_ERR(adv7511->cec_adap);
+
+ regmap_write(adv7511->regmap, ADV7511_REG_CEC_CTRL + offset, 0);
+ /* cec soft reset */
+ regmap_write(adv7511->regmap_cec,
+ ADV7511_REG_CEC_SOFT_RESET + offset, 0x01);
+ regmap_write(adv7511->regmap_cec,
+ ADV7511_REG_CEC_SOFT_RESET + offset, 0x00);
+
+ /* legacy mode */
+ regmap_write(adv7511->regmap_cec,
+ ADV7511_REG_CEC_RX_BUFFERS + offset, 0x00);
+
+ regmap_write(adv7511->regmap_cec,
+ ADV7511_REG_CEC_CLK_DIV + offset,
+ ((adv7511->cec_clk_freq / 750000) - 1) << 2);
+
+ ret = cec_register_adapter(adv7511->cec_adap, dev);
+ if (ret) {
+ cec_delete_adapter(adv7511->cec_adap);
+ adv7511->cec_adap = NULL;
+ }
+ return ret;
+}
diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c
index bd7dbae1119e..31ca883bda83 100644
--- a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c
+++ b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c
@@ -11,12 +11,15 @@
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/slab.h>
+#include <linux/clk.h>
#include <drm/drmP.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_edid.h>
+#include <media/cec.h>
+
#include "adv7511.h"
/* ADI recommended values for proper operation. */
@@ -336,8 +339,10 @@ static void __adv7511_power_on(struct adv7511 *adv7511)
*/
regmap_write(adv7511->regmap, ADV7511_REG_INT_ENABLE(0),
ADV7511_INT0_EDID_READY | ADV7511_INT0_HPD);
- regmap_write(adv7511->regmap, ADV7511_REG_INT_ENABLE(1),
- ADV7511_INT1_DDC_ERROR);
+ regmap_update_bits(adv7511->regmap,
+ ADV7511_REG_INT_ENABLE(1),
+ ADV7511_INT1_DDC_ERROR,
+ ADV7511_INT1_DDC_ERROR);
}
/*
@@ -373,6 +378,9 @@ static void __adv7511_power_off(struct adv7511 *adv7511)
regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER,
ADV7511_POWER_POWER_DOWN,
ADV7511_POWER_POWER_DOWN);
+ regmap_update_bits(adv7511->regmap,
+ ADV7511_REG_INT_ENABLE(1),
+ ADV7511_INT1_DDC_ERROR, 0);
regcache_mark_dirty(adv7511->regmap);
}
@@ -423,6 +431,8 @@ static void adv7511_hpd_work(struct work_struct *work)
if (adv7511->connector.status != status) {
adv7511->connector.status = status;
+ if (status == connector_status_disconnected)
+ cec_phys_addr_invalidate(adv7511->cec_adap);
drm_kms_helper_hotplug_event(adv7511->connector.dev);
}
}
@@ -453,6 +463,10 @@ static int adv7511_irq_process(struct adv7511 *adv7511, bool process_hpd)
wake_up_all(&adv7511->wq);
}
+#ifdef CONFIG_DRM_I2C_ADV7511_CEC
+ adv7511_cec_irq_process(adv7511, irq1);
+#endif
+
return 0;
}
@@ -595,6 +609,8 @@ static int adv7511_get_modes(struct adv7511 *adv7511,
kfree(edid);
+ cec_s_phys_addr_from_edid(adv7511->cec_adap, edid);
+
return count;
}
@@ -919,6 +935,65 @@ static void adv7511_uninit_regulators(struct adv7511 *adv)
regulator_bulk_disable(adv->num_supplies, adv->supplies);
}
+static bool adv7511_cec_register_volatile(struct device *dev, unsigned int reg)
+{
+ struct i2c_client *i2c = to_i2c_client(dev);
+ struct adv7511 *adv7511 = i2c_get_clientdata(i2c);
+
+ if (adv7511->type == ADV7533)
+ reg -= ADV7533_REG_CEC_OFFSET;
+
+ switch (reg) {
+ case ADV7511_REG_CEC_RX_FRAME_HDR:
+ case ADV7511_REG_CEC_RX_FRAME_DATA0...
+ ADV7511_REG_CEC_RX_FRAME_DATA0 + 14:
+ case ADV7511_REG_CEC_RX_FRAME_LEN:
+ case ADV7511_REG_CEC_RX_BUFFERS:
+ case ADV7511_REG_CEC_TX_LOW_DRV_CNT:
+ return true;
+ }
+
+ return false;
+}
+
+static const struct regmap_config adv7511_cec_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .max_register = 0xff,
+ .cache_type = REGCACHE_RBTREE,
+ .volatile_reg = adv7511_cec_register_volatile,
+};
+
+static int adv7511_init_cec_regmap(struct adv7511 *adv)
+{
+ int ret;
+
+ adv->i2c_cec = i2c_new_dummy(adv->i2c_main->adapter,
+ adv->i2c_main->addr - 1);
+ if (!adv->i2c_cec)
+ return -ENOMEM;
+ i2c_set_clientdata(adv->i2c_cec, adv);
+
+ adv->regmap_cec = devm_regmap_init_i2c(adv->i2c_cec,
+ &adv7511_cec_regmap_config);
+ if (IS_ERR(adv->regmap_cec)) {
+ ret = PTR_ERR(adv->regmap_cec);
+ goto err;
+ }
+
+ if (adv->type == ADV7533) {
+ ret = adv7533_patch_cec_registers(adv);
+ if (ret)
+ goto err;
+ }
+
+ return 0;
+err:
+ i2c_unregister_device(adv->i2c_cec);
+ return ret;
+}
+
static int adv7511_parse_dt(struct device_node *np,
struct adv7511_link_config *config)
{
@@ -1009,6 +1084,7 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id)
struct device *dev = &i2c->dev;
unsigned int main_i2c_addr = i2c->addr << 1;
unsigned int edid_i2c_addr = main_i2c_addr + 4;
+ unsigned int offset;
unsigned int val;
int ret;
@@ -1092,11 +1168,9 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id)
goto uninit_regulators;
}
- if (adv7511->type == ADV7533) {
- ret = adv7533_init_cec(adv7511);
- if (ret)
- goto err_i2c_unregister_edid;
- }
+ ret = adv7511_init_cec_regmap(adv7511);
+ if (ret)
+ goto err_i2c_unregister_edid;
INIT_WORK(&adv7511->hpd_work, adv7511_hpd_work);
@@ -1111,10 +1185,6 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id)
goto err_unregister_cec;
}
- /* CEC is unused for now */
- regmap_write(adv7511->regmap, ADV7511_REG_CEC_CTRL,
- ADV7511_CEC_CTRL_POWER_DOWN);
-
adv7511_power_off(adv7511);
i2c_set_clientdata(i2c, adv7511);
@@ -1129,10 +1199,23 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id)
adv7511_audio_init(dev, adv7511);
+ offset = adv7511->type == ADV7533 ? ADV7533_REG_CEC_OFFSET : 0;
+
+#ifdef CONFIG_DRM_I2C_ADV7511_CEC
+ ret = adv7511_cec_init(dev, adv7511, offset);
+ if (ret)
+ goto err_unregister_cec;
+#else
+ regmap_write(adv7511->regmap, ADV7511_REG_CEC_CTRL + offset,
+ ADV7511_CEC_CTRL_POWER_DOWN);
+#endif
+
return 0;
err_unregister_cec:
- adv7533_uninit_cec(adv7511);
+ i2c_unregister_device(adv7511->i2c_cec);
+ if (adv7511->cec_clk)
+ clk_disable_unprepare(adv7511->cec_clk);
err_i2c_unregister_edid:
i2c_unregister_device(adv7511->i2c_edid);
uninit_regulators:
@@ -1145,10 +1228,11 @@ static int adv7511_remove(struct i2c_client *i2c)
{
struct adv7511 *adv7511 = i2c_get_clientdata(i2c);
- if (adv7511->type == ADV7533) {
+ if (adv7511->type == ADV7533)
adv7533_detach_dsi(adv7511);
- adv7533_uninit_cec(adv7511);
- }
+ i2c_unregister_device(adv7511->i2c_cec);
+ if (adv7511->cec_clk)
+ clk_disable_unprepare(adv7511->cec_clk);
adv7511_uninit_regulators(adv7511);
@@ -1156,6 +1240,8 @@ static int adv7511_remove(struct i2c_client *i2c)
adv7511_audio_exit(adv7511);
+ cec_unregister_adapter(adv7511->cec_adap);
+
i2c_unregister_device(adv7511->i2c_edid);
return 0;
diff --git a/drivers/gpu/drm/bridge/adv7511/adv7533.c b/drivers/gpu/drm/bridge/adv7511/adv7533.c
index ac804f81e2f6..185b6d842166 100644
--- a/drivers/gpu/drm/bridge/adv7511/adv7533.c
+++ b/drivers/gpu/drm/bridge/adv7511/adv7533.c
@@ -32,14 +32,6 @@ static const struct reg_sequence adv7533_cec_fixed_registers[] = {
{ 0x05, 0xc8 },
};
-static const struct regmap_config adv7533_cec_regmap_config = {
- .reg_bits = 8,
- .val_bits = 8,
-
- .max_register = 0xff,
- .cache_type = REGCACHE_RBTREE,
-};
-
static void adv7511_dsi_config_timing_gen(struct adv7511 *adv)
{
struct mipi_dsi_device *dsi = adv->dsi;
@@ -145,37 +137,11 @@ int adv7533_patch_registers(struct adv7511 *adv)
ARRAY_SIZE(adv7533_fixed_registers));
}
-void adv7533_uninit_cec(struct adv7511 *adv)
-{
- i2c_unregister_device(adv->i2c_cec);
-}
-
-int adv7533_init_cec(struct adv7511 *adv)
+int adv7533_patch_cec_registers(struct adv7511 *adv)
{
- int ret;
-
- adv->i2c_cec = i2c_new_dummy(adv->i2c_main->adapter,
- adv->i2c_main->addr - 1);
- if (!adv->i2c_cec)
- return -ENOMEM;
-
- adv->regmap_cec = devm_regmap_init_i2c(adv->i2c_cec,
- &adv7533_cec_regmap_config);
- if (IS_ERR(adv->regmap_cec)) {
- ret = PTR_ERR(adv->regmap_cec);
- goto err;
- }
-
- ret = regmap_register_patch(adv->regmap_cec,
+ return regmap_register_patch(adv->regmap_cec,
adv7533_cec_fixed_registers,
ARRAY_SIZE(adv7533_cec_fixed_registers));
- if (ret)
- goto err;
-
- return 0;
-err:
- adv7533_uninit_cec(adv);
- return ret;
}
int adv7533_attach_dsi(struct adv7511 *adv)
diff --git a/drivers/gpu/drm/bridge/panel.c b/drivers/gpu/drm/bridge/panel.c
index e0cca19b4044..6d99d4a3beb3 100644
--- a/drivers/gpu/drm/bridge/panel.c
+++ b/drivers/gpu/drm/bridge/panel.c
@@ -188,7 +188,15 @@ EXPORT_SYMBOL(drm_panel_bridge_add);
*/
void drm_panel_bridge_remove(struct drm_bridge *bridge)
{
- struct panel_bridge *panel_bridge = drm_bridge_to_panel_bridge(bridge);
+ struct panel_bridge *panel_bridge;
+
+ if (!bridge)
+ return;
+
+ if (bridge->funcs != &panel_bridge_bridge_funcs)
+ return;
+
+ panel_bridge = drm_bridge_to_panel_bridge(bridge);
drm_bridge_remove(bridge);
devm_kfree(panel_bridge->panel->dev, bridge);
diff --git a/drivers/gpu/drm/bridge/sii9234.c b/drivers/gpu/drm/bridge/sii9234.c
new file mode 100644
index 000000000000..c77000626c22
--- /dev/null
+++ b/drivers/gpu/drm/bridge/sii9234.c
@@ -0,0 +1,994 @@
+/*
+ * Copyright (C) 2017 Samsung Electronics
+ *
+ * Authors:
+ * Tomasz Stanislawski <t.stanislaws@samsung.com>
+ * Maciej Purski <m.purski@samsung.com>
+ *
+ * Based on sii9234 driver created by:
+ * Adam Hampson <ahampson@sta.samsung.com>
+ * Erik Gilling <konkers@android.com>
+ * Shankar Bandal <shankar.b@samsung.com>
+ * Dharam Kumar <dharam.kr@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program
+ *
+ */
+#include <drm/bridge/mhl.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_edid.h>
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+
+#define CBUS_DEVCAP_OFFSET 0x80
+
+#define SII9234_MHL_VERSION 0x11
+#define SII9234_SCRATCHPAD_SIZE 0x10
+#define SII9234_INT_STAT_SIZE 0x33
+
+#define BIT_TMDS_CCTRL_TMDS_OE BIT(4)
+#define MHL_HPD_OUT_OVR_EN BIT(4)
+#define MHL_HPD_OUT_OVR_VAL BIT(5)
+#define MHL_INIT_TIMEOUT 0x0C
+
+/* MHL Tx registers and bits */
+#define MHL_TX_SRST 0x05
+#define MHL_TX_SYSSTAT_REG 0x09
+#define MHL_TX_INTR1_REG 0x71
+#define MHL_TX_INTR4_REG 0x74
+#define MHL_TX_INTR1_ENABLE_REG 0x75
+#define MHL_TX_INTR4_ENABLE_REG 0x78
+#define MHL_TX_INT_CTRL_REG 0x79
+#define MHL_TX_TMDS_CCTRL 0x80
+#define MHL_TX_DISC_CTRL1_REG 0x90
+#define MHL_TX_DISC_CTRL2_REG 0x91
+#define MHL_TX_DISC_CTRL3_REG 0x92
+#define MHL_TX_DISC_CTRL4_REG 0x93
+#define MHL_TX_DISC_CTRL5_REG 0x94
+#define MHL_TX_DISC_CTRL6_REG 0x95
+#define MHL_TX_DISC_CTRL7_REG 0x96
+#define MHL_TX_DISC_CTRL8_REG 0x97
+#define MHL_TX_STAT2_REG 0x99
+#define MHL_TX_MHLTX_CTL1_REG 0xA0
+#define MHL_TX_MHLTX_CTL2_REG 0xA1
+#define MHL_TX_MHLTX_CTL4_REG 0xA3
+#define MHL_TX_MHLTX_CTL6_REG 0xA5
+#define MHL_TX_MHLTX_CTL7_REG 0xA6
+
+#define RSEN_STATUS BIT(2)
+#define HPD_CHANGE_INT BIT(6)
+#define RSEN_CHANGE_INT BIT(5)
+#define RGND_READY_INT BIT(6)
+#define VBUS_LOW_INT BIT(5)
+#define CBUS_LKOUT_INT BIT(4)
+#define MHL_DISC_FAIL_INT BIT(3)
+#define MHL_EST_INT BIT(2)
+#define HPD_CHANGE_INT_MASK BIT(6)
+#define RSEN_CHANGE_INT_MASK BIT(5)
+
+#define RGND_READY_MASK BIT(6)
+#define CBUS_LKOUT_MASK BIT(4)
+#define MHL_DISC_FAIL_MASK BIT(3)
+#define MHL_EST_MASK BIT(2)
+
+#define SKIP_GND BIT(6)
+
+#define ATT_THRESH_SHIFT 0x04
+#define ATT_THRESH_MASK (0x03 << ATT_THRESH_SHIFT)
+#define USB_D_OEN BIT(3)
+#define DEGLITCH_TIME_MASK 0x07
+#define DEGLITCH_TIME_2MS 0
+#define DEGLITCH_TIME_4MS 1
+#define DEGLITCH_TIME_8MS 2
+#define DEGLITCH_TIME_16MS 3
+#define DEGLITCH_TIME_40MS 4
+#define DEGLITCH_TIME_50MS 5
+#define DEGLITCH_TIME_60MS 6
+#define DEGLITCH_TIME_128MS 7
+
+#define USB_D_OVR BIT(7)
+#define USB_ID_OVR BIT(6)
+#define DVRFLT_SEL BIT(5)
+#define BLOCK_RGND_INT BIT(4)
+#define SKIP_DEG BIT(3)
+#define CI2CA_POL BIT(2)
+#define CI2CA_WKUP BIT(1)
+#define SINGLE_ATT BIT(0)
+
+#define USB_D_ODN BIT(5)
+#define VBUS_CHECK BIT(2)
+#define RGND_INTP_MASK 0x03
+#define RGND_INTP_OPEN 0
+#define RGND_INTP_2K 1
+#define RGND_INTP_1K 2
+#define RGND_INTP_SHORT 3
+
+/* HDMI registers */
+#define HDMI_RX_TMDS0_CCTRL1_REG 0x10
+#define HDMI_RX_TMDS_CLK_EN_REG 0x11
+#define HDMI_RX_TMDS_CH_EN_REG 0x12
+#define HDMI_RX_PLL_CALREFSEL_REG 0x17
+#define HDMI_RX_PLL_VCOCAL_REG 0x1A
+#define HDMI_RX_EQ_DATA0_REG 0x22
+#define HDMI_RX_EQ_DATA1_REG 0x23
+#define HDMI_RX_EQ_DATA2_REG 0x24
+#define HDMI_RX_EQ_DATA3_REG 0x25
+#define HDMI_RX_EQ_DATA4_REG 0x26
+#define HDMI_RX_TMDS_ZONE_CTRL_REG 0x4C
+#define HDMI_RX_TMDS_MODE_CTRL_REG 0x4D
+
+/* CBUS registers */
+#define CBUS_INT_STATUS_1_REG 0x08
+#define CBUS_INTR1_ENABLE_REG 0x09
+#define CBUS_MSC_REQ_ABORT_REASON_REG 0x0D
+#define CBUS_INT_STATUS_2_REG 0x1E
+#define CBUS_INTR2_ENABLE_REG 0x1F
+#define CBUS_LINK_CONTROL_2_REG 0x31
+#define CBUS_MHL_STATUS_REG_0 0xB0
+#define CBUS_MHL_STATUS_REG_1 0xB1
+
+#define BIT_CBUS_RESET BIT(3)
+#define SET_HPD_DOWNSTREAM BIT(6)
+
+/* TPI registers */
+#define TPI_DPD_REG 0x3D
+
+/* Timeouts in msec */
+#define T_SRC_VBUS_CBUS_TO_STABLE 200
+#define T_SRC_CBUS_FLOAT 100
+#define T_SRC_CBUS_DEGLITCH 2
+#define T_SRC_RXSENSE_DEGLITCH 110
+
+#define MHL1_MAX_CLK 75000 /* in kHz */
+
+#define I2C_TPI_ADDR 0x3D
+#define I2C_HDMI_ADDR 0x49
+#define I2C_CBUS_ADDR 0x64
+
+enum sii9234_state {
+ ST_OFF,
+ ST_D3,
+ ST_RGND_INIT,
+ ST_RGND_1K,
+ ST_RSEN_HIGH,
+ ST_MHL_ESTABLISHED,
+ ST_FAILURE_DISCOVERY,
+ ST_FAILURE,
+};
+
+struct sii9234 {
+ struct i2c_client *client[4];
+ struct drm_bridge bridge;
+ struct device *dev;
+ struct gpio_desc *gpio_reset;
+ int i2c_error;
+ struct regulator_bulk_data supplies[4];
+
+ struct mutex lock; /* Protects fields below and device registers */
+ enum sii9234_state state;
+};
+
+enum sii9234_client_id {
+ I2C_MHL,
+ I2C_TPI,
+ I2C_HDMI,
+ I2C_CBUS,
+};
+
+static const char * const sii9234_client_name[] = {
+ [I2C_MHL] = "MHL",
+ [I2C_TPI] = "TPI",
+ [I2C_HDMI] = "HDMI",
+ [I2C_CBUS] = "CBUS",
+};
+
+static int sii9234_writeb(struct sii9234 *ctx, int id, int offset,
+ int value)
+{
+ int ret;
+ struct i2c_client *client = ctx->client[id];
+
+ if (ctx->i2c_error)
+ return ctx->i2c_error;
+
+ ret = i2c_smbus_write_byte_data(client, offset, value);
+ if (ret < 0)
+ dev_err(ctx->dev, "writeb: %4s[0x%02x] <- 0x%02x\n",
+ sii9234_client_name[id], offset, value);
+ ctx->i2c_error = ret;
+
+ return ret;
+}
+
+static int sii9234_writebm(struct sii9234 *ctx, int id, int offset,
+ int value, int mask)
+{
+ int ret;
+ struct i2c_client *client = ctx->client[id];
+
+ if (ctx->i2c_error)
+ return ctx->i2c_error;
+
+ ret = i2c_smbus_write_byte(client, offset);
+ if (ret < 0) {
+ dev_err(ctx->dev, "writebm: %4s[0x%02x] <- 0x%02x\n",
+ sii9234_client_name[id], offset, value);
+ ctx->i2c_error = ret;
+ return ret;
+ }
+
+ ret = i2c_smbus_read_byte(client);
+ if (ret < 0) {
+ dev_err(ctx->dev, "writebm: %4s[0x%02x] <- 0x%02x\n",
+ sii9234_client_name[id], offset, value);
+ ctx->i2c_error = ret;
+ return ret;
+ }
+
+ value = (value & mask) | (ret & ~mask);
+
+ ret = i2c_smbus_write_byte_data(client, offset, value);
+ if (ret < 0) {
+ dev_err(ctx->dev, "writebm: %4s[0x%02x] <- 0x%02x\n",
+ sii9234_client_name[id], offset, value);
+ ctx->i2c_error = ret;
+ }
+
+ return ret;
+}
+
+static int sii9234_readb(struct sii9234 *ctx, int id, int offset)
+{
+ int ret;
+ struct i2c_client *client = ctx->client[id];
+
+ if (ctx->i2c_error)
+ return ctx->i2c_error;
+
+ ret = i2c_smbus_write_byte(client, offset);
+ if (ret < 0) {
+ dev_err(ctx->dev, "readb: %4s[0x%02x]\n",
+ sii9234_client_name[id], offset);
+ ctx->i2c_error = ret;
+ return ret;
+ }
+
+ ret = i2c_smbus_read_byte(client);
+ if (ret < 0) {
+ dev_err(ctx->dev, "readb: %4s[0x%02x]\n",
+ sii9234_client_name[id], offset);
+ ctx->i2c_error = ret;
+ }
+
+ return ret;
+}
+
+static int sii9234_clear_error(struct sii9234 *ctx)
+{
+ int ret = ctx->i2c_error;
+
+ ctx->i2c_error = 0;
+
+ return ret;
+}
+
+#define mhl_tx_writeb(sii9234, offset, value) \
+ sii9234_writeb(sii9234, I2C_MHL, offset, value)
+#define mhl_tx_writebm(sii9234, offset, value, mask) \
+ sii9234_writebm(sii9234, I2C_MHL, offset, value, mask)
+#define mhl_tx_readb(sii9234, offset) \
+ sii9234_readb(sii9234, I2C_MHL, offset)
+#define cbus_writeb(sii9234, offset, value) \
+ sii9234_writeb(sii9234, I2C_CBUS, offset, value)
+#define cbus_writebm(sii9234, offset, value, mask) \
+ sii9234_writebm(sii9234, I2C_CBUS, offset, value, mask)
+#define cbus_readb(sii9234, offset) \
+ sii9234_readb(sii9234, I2C_CBUS, offset)
+#define hdmi_writeb(sii9234, offset, value) \
+ sii9234_writeb(sii9234, I2C_HDMI, offset, value)
+#define hdmi_writebm(sii9234, offset, value, mask) \
+ sii9234_writebm(sii9234, I2C_HDMI, offset, value, mask)
+#define hdmi_readb(sii9234, offset) \
+ sii9234_readb(sii9234, I2C_HDMI, offset)
+#define tpi_writeb(sii9234, offset, value) \
+ sii9234_writeb(sii9234, I2C_TPI, offset, value)
+#define tpi_writebm(sii9234, offset, value, mask) \
+ sii9234_writebm(sii9234, I2C_TPI, offset, value, mask)
+#define tpi_readb(sii9234, offset) \
+ sii9234_readb(sii9234, I2C_TPI, offset)
+
+static u8 sii9234_tmds_control(struct sii9234 *ctx, bool enable)
+{
+ mhl_tx_writebm(ctx, MHL_TX_TMDS_CCTRL, enable ? ~0 : 0,
+ BIT_TMDS_CCTRL_TMDS_OE);
+ mhl_tx_writebm(ctx, MHL_TX_INT_CTRL_REG, enable ? ~0 : 0,
+ MHL_HPD_OUT_OVR_EN | MHL_HPD_OUT_OVR_VAL);
+ return sii9234_clear_error(ctx);
+}
+
+static int sii9234_cbus_reset(struct sii9234 *ctx)
+{
+ int i;
+
+ mhl_tx_writebm(ctx, MHL_TX_SRST, ~0, BIT_CBUS_RESET);
+ msleep(T_SRC_CBUS_DEGLITCH);
+ mhl_tx_writebm(ctx, MHL_TX_SRST, 0, BIT_CBUS_RESET);
+
+ for (i = 0; i < 4; i++) {
+ /*
+ * Enable WRITE_STAT interrupt for writes to all
+ * 4 MSC Status registers.
+ */
+ cbus_writeb(ctx, 0xE0 + i, 0xF2);
+ /*
+ * Enable SET_INT interrupt for writes to all
+ * 4 MSC Interrupt registers.
+ */
+ cbus_writeb(ctx, 0xF0 + i, 0xF2);
+ }
+
+ return sii9234_clear_error(ctx);
+}
+
+/* Require to chek mhl imformation of samsung in cbus_init_register */
+static int sii9234_cbus_init(struct sii9234 *ctx)
+{
+ cbus_writeb(ctx, 0x07, 0xF2);
+ cbus_writeb(ctx, 0x40, 0x03);
+ cbus_writeb(ctx, 0x42, 0x06);
+ cbus_writeb(ctx, 0x36, 0x0C);
+ cbus_writeb(ctx, 0x3D, 0xFD);
+ cbus_writeb(ctx, 0x1C, 0x01);
+ cbus_writeb(ctx, 0x1D, 0x0F);
+ cbus_writeb(ctx, 0x44, 0x02);
+ /* Setup our devcap */
+ cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_DEV_STATE, 0x00);
+ cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_MHL_VERSION,
+ SII9234_MHL_VERSION);
+ cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_CAT,
+ MHL_DCAP_CAT_SOURCE);
+ cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_ADOPTER_ID_H, 0x01);
+ cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_ADOPTER_ID_L, 0x41);
+ cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_VID_LINK_MODE,
+ MHL_DCAP_VID_LINK_RGB444 | MHL_DCAP_VID_LINK_YCBCR444);
+ cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_VIDEO_TYPE,
+ MHL_DCAP_VT_GRAPHICS);
+ cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_LOG_DEV_MAP,
+ MHL_DCAP_LD_GUI);
+ cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_BANDWIDTH, 0x0F);
+ cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_FEATURE_FLAG,
+ MHL_DCAP_FEATURE_RCP_SUPPORT | MHL_DCAP_FEATURE_RAP_SUPPORT
+ | MHL_DCAP_FEATURE_SP_SUPPORT);
+ cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_DEVICE_ID_H, 0x0);
+ cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_DEVICE_ID_L, 0x0);
+ cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_SCRATCHPAD_SIZE,
+ SII9234_SCRATCHPAD_SIZE);
+ cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_INT_STAT_SIZE,
+ SII9234_INT_STAT_SIZE);
+ cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_RESERVED, 0);
+ cbus_writebm(ctx, 0x31, 0x0C, 0x0C);
+ cbus_writeb(ctx, 0x30, 0x01);
+ cbus_writebm(ctx, 0x3C, 0x30, 0x38);
+ cbus_writebm(ctx, 0x22, 0x0D, 0x0F);
+ cbus_writebm(ctx, 0x2E, 0x15, 0x15);
+ cbus_writeb(ctx, CBUS_INTR1_ENABLE_REG, 0);
+ cbus_writeb(ctx, CBUS_INTR2_ENABLE_REG, 0);
+
+ return sii9234_clear_error(ctx);
+}
+
+static void force_usb_id_switch_open(struct sii9234 *ctx)
+{
+ /* Disable CBUS discovery */
+ mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL1_REG, 0, 0x01);
+ /* Force USB ID switch to open */
+ mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL6_REG, ~0, USB_ID_OVR);
+ mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL3_REG, ~0, 0x86);
+ /* Force upstream HPD to 0 when not in MHL mode. */
+ mhl_tx_writebm(ctx, MHL_TX_INT_CTRL_REG, 0, 0x30);
+}
+
+static void release_usb_id_switch_open(struct sii9234 *ctx)
+{
+ msleep(T_SRC_CBUS_FLOAT);
+ /* Clear USB ID switch to open */
+ mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL6_REG, 0, USB_ID_OVR);
+ /* Enable CBUS discovery */
+ mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL1_REG, ~0, 0x01);
+}
+
+static int sii9234_power_init(struct sii9234 *ctx)
+{
+ /* Force the SiI9234 into the D0 state. */
+ tpi_writeb(ctx, TPI_DPD_REG, 0x3F);
+ /* Enable TxPLL Clock */
+ hdmi_writeb(ctx, HDMI_RX_TMDS_CLK_EN_REG, 0x01);
+ /* Enable Tx Clock Path & Equalizer */
+ hdmi_writeb(ctx, HDMI_RX_TMDS_CH_EN_REG, 0x15);
+ /* Power Up TMDS */
+ mhl_tx_writeb(ctx, 0x08, 0x35);
+ return sii9234_clear_error(ctx);
+}
+
+static int sii9234_hdmi_init(struct sii9234 *ctx)
+{
+ hdmi_writeb(ctx, HDMI_RX_TMDS0_CCTRL1_REG, 0xC1);
+ hdmi_writeb(ctx, HDMI_RX_PLL_CALREFSEL_REG, 0x03);
+ hdmi_writeb(ctx, HDMI_RX_PLL_VCOCAL_REG, 0x20);
+ hdmi_writeb(ctx, HDMI_RX_EQ_DATA0_REG, 0x8A);
+ hdmi_writeb(ctx, HDMI_RX_EQ_DATA1_REG, 0x6A);
+ hdmi_writeb(ctx, HDMI_RX_EQ_DATA2_REG, 0xAA);
+ hdmi_writeb(ctx, HDMI_RX_EQ_DATA3_REG, 0xCA);
+ hdmi_writeb(ctx, HDMI_RX_EQ_DATA4_REG, 0xEA);
+ hdmi_writeb(ctx, HDMI_RX_TMDS_ZONE_CTRL_REG, 0xA0);
+ hdmi_writeb(ctx, HDMI_RX_TMDS_MODE_CTRL_REG, 0x00);
+ mhl_tx_writeb(ctx, MHL_TX_TMDS_CCTRL, 0x34);
+ hdmi_writeb(ctx, 0x45, 0x44);
+ hdmi_writeb(ctx, 0x31, 0x0A);
+ hdmi_writeb(ctx, HDMI_RX_TMDS0_CCTRL1_REG, 0xC1);
+
+ return sii9234_clear_error(ctx);
+}
+
+static int sii9234_mhl_tx_ctl_int(struct sii9234 *ctx)
+{
+ mhl_tx_writeb(ctx, MHL_TX_MHLTX_CTL1_REG, 0xD0);
+ mhl_tx_writeb(ctx, MHL_TX_MHLTX_CTL2_REG, 0xFC);
+ mhl_tx_writeb(ctx, MHL_TX_MHLTX_CTL4_REG, 0xEB);
+ mhl_tx_writeb(ctx, MHL_TX_MHLTX_CTL7_REG, 0x0C);
+
+ return sii9234_clear_error(ctx);
+}
+
+static int sii9234_reset(struct sii9234 *ctx)
+{
+ int ret;
+
+ sii9234_clear_error(ctx);
+
+ ret = sii9234_power_init(ctx);
+ if (ret < 0)
+ return ret;
+ ret = sii9234_cbus_reset(ctx);
+ if (ret < 0)
+ return ret;
+ ret = sii9234_hdmi_init(ctx);
+ if (ret < 0)
+ return ret;
+ ret = sii9234_mhl_tx_ctl_int(ctx);
+ if (ret < 0)
+ return ret;
+
+ /* Enable HDCP Compliance safety */
+ mhl_tx_writeb(ctx, 0x2B, 0x01);
+ /* CBUS discovery cycle time for each drive and float = 150us */
+ mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL1_REG, 0x04, 0x06);
+ /* Clear bit 6 (reg_skip_rgnd) */
+ mhl_tx_writeb(ctx, MHL_TX_DISC_CTRL2_REG, (1 << 7) /* Reserved */
+ | 2 << ATT_THRESH_SHIFT | DEGLITCH_TIME_50MS);
+ /*
+ * Changed from 66 to 65 for 94[1:0] = 01 = 5k reg_cbusmhl_pup_sel
+ * 1.8V CBUS VTH & GND threshold
+ * to meet CTS 3.3.7.2 spec
+ */
+ mhl_tx_writeb(ctx, MHL_TX_DISC_CTRL5_REG, 0x77);
+ cbus_writebm(ctx, CBUS_LINK_CONTROL_2_REG, ~0, MHL_INIT_TIMEOUT);
+ mhl_tx_writeb(ctx, MHL_TX_MHLTX_CTL6_REG, 0xA0);
+ /* RGND & single discovery attempt (RGND blocking) */
+ mhl_tx_writeb(ctx, MHL_TX_DISC_CTRL6_REG, BLOCK_RGND_INT |
+ DVRFLT_SEL | SINGLE_ATT);
+ /* Use VBUS path of discovery state machine */
+ mhl_tx_writeb(ctx, MHL_TX_DISC_CTRL8_REG, 0);
+ /* 0x92[3] sets the CBUS / ID switch */
+ mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL6_REG, ~0, USB_ID_OVR);
+ /*
+ * To allow RGND engine to operate correctly.
+ * When moving the chip from D2 to D0 (power up, init regs)
+ * the values should be
+ * 94[1:0] = 01 reg_cbusmhl_pup_sel[1:0] should be set for 5k
+ * 93[7:6] = 10 reg_cbusdisc_pup_sel[1:0] should be
+ * set for 10k (default)
+ * 93[5:4] = 00 reg_cbusidle_pup_sel[1:0] = open (default)
+ */
+ mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL3_REG, ~0, 0x86);
+ /*
+ * Change from CC to 8C to match 5K
+ * to meet CTS 3.3.72 spec
+ */
+ mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL4_REG, ~0, 0x8C);
+ /* Configure the interrupt as active high */
+ mhl_tx_writebm(ctx, MHL_TX_INT_CTRL_REG, 0, 0x06);
+
+ msleep(25);
+
+ /* Release usb_id switch */
+ mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL6_REG, 0, USB_ID_OVR);
+ mhl_tx_writeb(ctx, MHL_TX_DISC_CTRL1_REG, 0x27);
+
+ ret = sii9234_clear_error(ctx);
+ if (ret < 0)
+ return ret;
+ ret = sii9234_cbus_init(ctx);
+ if (ret < 0)
+ return ret;
+
+ /* Enable Auto soft reset on SCDT = 0 */
+ mhl_tx_writeb(ctx, 0x05, 0x04);
+ /* HDMI Transcode mode enable */
+ mhl_tx_writeb(ctx, 0x0D, 0x1C);
+ mhl_tx_writeb(ctx, MHL_TX_INTR4_ENABLE_REG,
+ RGND_READY_MASK | CBUS_LKOUT_MASK
+ | MHL_DISC_FAIL_MASK | MHL_EST_MASK);
+ mhl_tx_writeb(ctx, MHL_TX_INTR1_ENABLE_REG, 0x60);
+
+ /* This point is very important before measure RGND impedance */
+ force_usb_id_switch_open(ctx);
+ mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL4_REG, 0, 0xF0);
+ mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL5_REG, 0, 0x03);
+ release_usb_id_switch_open(ctx);
+
+ /* Force upstream HPD to 0 when not in MHL mode */
+ mhl_tx_writebm(ctx, MHL_TX_INT_CTRL_REG, 0, 1 << 5);
+ mhl_tx_writebm(ctx, MHL_TX_INT_CTRL_REG, ~0, 1 << 4);
+
+ return sii9234_clear_error(ctx);
+}
+
+static int sii9234_goto_d3(struct sii9234 *ctx)
+{
+ int ret;
+
+ dev_dbg(ctx->dev, "sii9234: detection started d3\n");
+
+ ret = sii9234_reset(ctx);
+ if (ret < 0)
+ goto exit;
+
+ hdmi_writeb(ctx, 0x01, 0x03);
+ tpi_writebm(ctx, TPI_DPD_REG, 0, 1);
+ /* I2C above is expected to fail because power goes down */
+ sii9234_clear_error(ctx);
+
+ ctx->state = ST_D3;
+
+ return 0;
+ exit:
+ dev_err(ctx->dev, "%s failed\n", __func__);
+ return -1;
+}
+
+static int sii9234_hw_on(struct sii9234 *ctx)
+{
+ return regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
+}
+
+static void sii9234_hw_off(struct sii9234 *ctx)
+{
+ gpiod_set_value(ctx->gpio_reset, 1);
+ msleep(20);
+ regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
+}
+
+static void sii9234_hw_reset(struct sii9234 *ctx)
+{
+ gpiod_set_value(ctx->gpio_reset, 1);
+ msleep(20);
+ gpiod_set_value(ctx->gpio_reset, 0);
+}
+
+static void sii9234_cable_in(struct sii9234 *ctx)
+{
+ int ret;
+
+ mutex_lock(&ctx->lock);
+ if (ctx->state != ST_OFF)
+ goto unlock;
+ ret = sii9234_hw_on(ctx);
+ if (ret < 0)
+ goto unlock;
+
+ sii9234_hw_reset(ctx);
+ sii9234_goto_d3(ctx);
+ /* To avoid irq storm, when hw is in meta state */
+ enable_irq(to_i2c_client(ctx->dev)->irq);
+
+unlock:
+ mutex_unlock(&ctx->lock);
+}
+
+static void sii9234_cable_out(struct sii9234 *ctx)
+{
+ mutex_lock(&ctx->lock);
+
+ if (ctx->state == ST_OFF)
+ goto unlock;
+
+ disable_irq(to_i2c_client(ctx->dev)->irq);
+ tpi_writeb(ctx, TPI_DPD_REG, 0);
+ /* Turn on&off hpd festure for only QCT HDMI */
+ sii9234_hw_off(ctx);
+
+ ctx->state = ST_OFF;
+
+unlock:
+ mutex_unlock(&ctx->lock);
+}
+
+static enum sii9234_state sii9234_rgnd_ready_irq(struct sii9234 *ctx)
+{
+ int value;
+
+ if (ctx->state == ST_D3) {
+ int ret;
+
+ dev_dbg(ctx->dev, "RGND_READY_INT\n");
+ sii9234_hw_reset(ctx);
+
+ ret = sii9234_reset(ctx);
+ if (ret < 0) {
+ dev_err(ctx->dev, "sii9234_reset() failed\n");
+ return ST_FAILURE;
+ }
+
+ return ST_RGND_INIT;
+ }
+
+ /* Got interrupt in inappropriate state */
+ if (ctx->state != ST_RGND_INIT)
+ return ST_FAILURE;
+
+ value = mhl_tx_readb(ctx, MHL_TX_STAT2_REG);
+ if (sii9234_clear_error(ctx))
+ return ST_FAILURE;
+
+ if ((value & RGND_INTP_MASK) != RGND_INTP_1K) {
+ dev_warn(ctx->dev, "RGND is not 1k\n");
+ return ST_RGND_INIT;
+ }
+ dev_dbg(ctx->dev, "RGND 1K!!\n");
+ mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL4_REG, ~0, 0x8C);
+ mhl_tx_writeb(ctx, MHL_TX_DISC_CTRL5_REG, 0x77);
+ mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL6_REG, ~0, 0x05);
+ if (sii9234_clear_error(ctx))
+ return ST_FAILURE;
+
+ msleep(T_SRC_VBUS_CBUS_TO_STABLE);
+ return ST_RGND_1K;
+}
+
+static enum sii9234_state sii9234_mhl_established(struct sii9234 *ctx)
+{
+ dev_dbg(ctx->dev, "mhl est interrupt\n");
+
+ /* Discovery override */
+ mhl_tx_writeb(ctx, MHL_TX_MHLTX_CTL1_REG, 0x10);
+ /* Increase DDC translation layer timer (byte mode) */
+ cbus_writeb(ctx, 0x07, 0x32);
+ cbus_writebm(ctx, 0x44, ~0, 1 << 1);
+ /* Keep the discovery enabled. Need RGND interrupt */
+ mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL1_REG, ~0, 1);
+ mhl_tx_writeb(ctx, MHL_TX_INTR1_ENABLE_REG,
+ RSEN_CHANGE_INT_MASK | HPD_CHANGE_INT_MASK);
+
+ if (sii9234_clear_error(ctx))
+ return ST_FAILURE;
+
+ return ST_MHL_ESTABLISHED;
+}
+
+static enum sii9234_state sii9234_hpd_change(struct sii9234 *ctx)
+{
+ int value;
+
+ value = cbus_readb(ctx, CBUS_MSC_REQ_ABORT_REASON_REG);
+ if (sii9234_clear_error(ctx))
+ return ST_FAILURE;
+
+ if (value & SET_HPD_DOWNSTREAM) {
+ /* Downstream HPD High, Enable TMDS */
+ sii9234_tmds_control(ctx, true);
+ } else {
+ /* Downstream HPD Low, Disable TMDS */
+ sii9234_tmds_control(ctx, false);
+ }
+
+ return ctx->state;
+}
+
+static enum sii9234_state sii9234_rsen_change(struct sii9234 *ctx)
+{
+ int value;
+
+ /* Work_around code to handle wrong interrupt */
+ if (ctx->state != ST_RGND_1K) {
+ dev_err(ctx->dev, "RSEN_HIGH without RGND_1K\n");
+ return ST_FAILURE;
+ }
+ value = mhl_tx_readb(ctx, MHL_TX_SYSSTAT_REG);
+ if (value < 0)
+ return ST_FAILURE;
+
+ if (value & RSEN_STATUS) {
+ dev_dbg(ctx->dev, "MHL cable connected.. RSEN High\n");
+ return ST_RSEN_HIGH;
+ }
+ dev_dbg(ctx->dev, "RSEN lost\n");
+ /*
+ * Once RSEN loss is confirmed,we need to check
+ * based on cable status and chip power status,whether
+ * it is SINK Loss(HDMI cable not connected, TV Off)
+ * or MHL cable disconnection
+ * TODO: Define the below mhl_disconnection()
+ */
+ msleep(T_SRC_RXSENSE_DEGLITCH);
+ value = mhl_tx_readb(ctx, MHL_TX_SYSSTAT_REG);
+ if (value < 0)
+ return ST_FAILURE;
+ dev_dbg(ctx->dev, "sys_stat: %x\n", value);
+
+ if (value & RSEN_STATUS) {
+ dev_dbg(ctx->dev, "RSEN recovery\n");
+ return ST_RSEN_HIGH;
+ }
+ dev_dbg(ctx->dev, "RSEN Really LOW\n");
+ /* To meet CTS 3.3.22.2 spec */
+ sii9234_tmds_control(ctx, false);
+ force_usb_id_switch_open(ctx);
+ release_usb_id_switch_open(ctx);
+
+ return ST_FAILURE;
+}
+
+static irqreturn_t sii9234_irq_thread(int irq, void *data)
+{
+ struct sii9234 *ctx = data;
+ int intr1, intr4;
+ int intr1_en, intr4_en;
+ int cbus_intr1, cbus_intr2;
+
+ dev_dbg(ctx->dev, "%s\n", __func__);
+
+ mutex_lock(&ctx->lock);
+
+ intr1 = mhl_tx_readb(ctx, MHL_TX_INTR1_REG);
+ intr4 = mhl_tx_readb(ctx, MHL_TX_INTR4_REG);
+ intr1_en = mhl_tx_readb(ctx, MHL_TX_INTR1_ENABLE_REG);
+ intr4_en = mhl_tx_readb(ctx, MHL_TX_INTR4_ENABLE_REG);
+ cbus_intr1 = cbus_readb(ctx, CBUS_INT_STATUS_1_REG);
+ cbus_intr2 = cbus_readb(ctx, CBUS_INT_STATUS_2_REG);
+
+ if (sii9234_clear_error(ctx))
+ goto done;
+
+ dev_dbg(ctx->dev, "irq %02x/%02x %02x/%02x %02x/%02x\n",
+ intr1, intr1_en, intr4, intr4_en, cbus_intr1, cbus_intr2);
+
+ if (intr4 & RGND_READY_INT)
+ ctx->state = sii9234_rgnd_ready_irq(ctx);
+ if (intr1 & RSEN_CHANGE_INT)
+ ctx->state = sii9234_rsen_change(ctx);
+ if (intr4 & MHL_EST_INT)
+ ctx->state = sii9234_mhl_established(ctx);
+ if (intr1 & HPD_CHANGE_INT)
+ ctx->state = sii9234_hpd_change(ctx);
+ if (intr4 & CBUS_LKOUT_INT)
+ ctx->state = ST_FAILURE;
+ if (intr4 & MHL_DISC_FAIL_INT)
+ ctx->state = ST_FAILURE_DISCOVERY;
+
+ done:
+ /* Clean interrupt status and pending flags */
+ mhl_tx_writeb(ctx, MHL_TX_INTR1_REG, intr1);
+ mhl_tx_writeb(ctx, MHL_TX_INTR4_REG, intr4);
+ cbus_writeb(ctx, CBUS_MHL_STATUS_REG_0, 0xFF);
+ cbus_writeb(ctx, CBUS_MHL_STATUS_REG_1, 0xFF);
+ cbus_writeb(ctx, CBUS_INT_STATUS_1_REG, cbus_intr1);
+ cbus_writeb(ctx, CBUS_INT_STATUS_2_REG, cbus_intr2);
+
+ sii9234_clear_error(ctx);
+
+ if (ctx->state == ST_FAILURE) {
+ dev_dbg(ctx->dev, "try to reset after failure\n");
+ sii9234_hw_reset(ctx);
+ sii9234_goto_d3(ctx);
+ }
+
+ if (ctx->state == ST_FAILURE_DISCOVERY) {
+ dev_err(ctx->dev, "discovery failed, no power for MHL?\n");
+ tpi_writebm(ctx, TPI_DPD_REG, 0, 1);
+ ctx->state = ST_D3;
+ }
+
+ mutex_unlock(&ctx->lock);
+
+ return IRQ_HANDLED;
+}
+
+static int sii9234_init_resources(struct sii9234 *ctx,
+ struct i2c_client *client)
+{
+ struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+ int ret;
+
+ if (!ctx->dev->of_node) {
+ dev_err(ctx->dev, "not DT device\n");
+ return -ENODEV;
+ }
+
+ ctx->gpio_reset = devm_gpiod_get(ctx->dev, "reset", GPIOD_OUT_LOW);
+ if (IS_ERR(ctx->gpio_reset)) {
+ dev_err(ctx->dev, "failed to get reset gpio from DT\n");
+ return PTR_ERR(ctx->gpio_reset);
+ }
+
+ ctx->supplies[0].supply = "avcc12";
+ ctx->supplies[1].supply = "avcc33";
+ ctx->supplies[2].supply = "iovcc18";
+ ctx->supplies[3].supply = "cvcc12";
+ ret = devm_regulator_bulk_get(ctx->dev, 4, ctx->supplies);
+ if (ret) {
+ dev_err(ctx->dev, "regulator_bulk failed\n");
+ return ret;
+ }
+
+ ctx->client[I2C_MHL] = client;
+
+ ctx->client[I2C_TPI] = i2c_new_dummy(adapter, I2C_TPI_ADDR);
+ if (!ctx->client[I2C_TPI]) {
+ dev_err(ctx->dev, "failed to create TPI client\n");
+ return -ENODEV;
+ }
+
+ ctx->client[I2C_HDMI] = i2c_new_dummy(adapter, I2C_HDMI_ADDR);
+ if (!ctx->client[I2C_HDMI]) {
+ dev_err(ctx->dev, "failed to create HDMI RX client\n");
+ goto fail_tpi;
+ }
+
+ ctx->client[I2C_CBUS] = i2c_new_dummy(adapter, I2C_CBUS_ADDR);
+ if (!ctx->client[I2C_CBUS]) {
+ dev_err(ctx->dev, "failed to create CBUS client\n");
+ goto fail_hdmi;
+ }
+
+ return 0;
+
+fail_hdmi:
+ i2c_unregister_device(ctx->client[I2C_HDMI]);
+fail_tpi:
+ i2c_unregister_device(ctx->client[I2C_TPI]);
+
+ return -ENODEV;
+}
+
+static void sii9234_deinit_resources(struct sii9234 *ctx)
+{
+ i2c_unregister_device(ctx->client[I2C_CBUS]);
+ i2c_unregister_device(ctx->client[I2C_HDMI]);
+ i2c_unregister_device(ctx->client[I2C_TPI]);
+}
+
+static inline struct sii9234 *bridge_to_sii9234(struct drm_bridge *bridge)
+{
+ return container_of(bridge, struct sii9234, bridge);
+}
+
+static enum drm_mode_status sii9234_mode_valid(struct drm_bridge *bridge,
+ const struct drm_display_mode *mode)
+{
+ if (mode->clock > MHL1_MAX_CLK)
+ return MODE_CLOCK_HIGH;
+
+ return MODE_OK;
+}
+
+static const struct drm_bridge_funcs sii9234_bridge_funcs = {
+ .mode_valid = sii9234_mode_valid,
+};
+
+static int sii9234_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+ struct sii9234 *ctx;
+ struct device *dev = &client->dev;
+ int ret;
+
+ ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ ctx->dev = dev;
+ mutex_init(&ctx->lock);
+
+ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
+ dev_err(dev, "I2C adapter lacks SMBUS feature\n");
+ return -EIO;
+ }
+
+ if (!client->irq) {
+ dev_err(dev, "no irq provided\n");
+ return -EINVAL;
+ }
+
+ irq_set_status_flags(client->irq, IRQ_NOAUTOEN);
+ ret = devm_request_threaded_irq(dev, client->irq, NULL,
+ sii9234_irq_thread,
+ IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
+ "sii9234", ctx);
+ if (ret < 0) {
+ dev_err(dev, "failed to install IRQ handler\n");
+ return ret;
+ }
+
+ ret = sii9234_init_resources(ctx, client);
+ if (ret < 0)
+ return ret;
+
+ i2c_set_clientdata(client, ctx);
+
+ ctx->bridge.funcs = &sii9234_bridge_funcs;
+ ctx->bridge.of_node = dev->of_node;
+ drm_bridge_add(&ctx->bridge);
+
+ sii9234_cable_in(ctx);
+
+ return 0;
+}
+
+static int sii9234_remove(struct i2c_client *client)
+{
+ struct sii9234 *ctx = i2c_get_clientdata(client);
+
+ sii9234_cable_out(ctx);
+ drm_bridge_remove(&ctx->bridge);
+ sii9234_deinit_resources(ctx);
+
+ return 0;
+}
+
+static const struct of_device_id sii9234_dt_match[] = {
+ { .compatible = "sil,sii9234" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, sii9234_dt_match);
+
+static const struct i2c_device_id sii9234_id[] = {
+ { "SII9234", 0 },
+ { },
+};
+MODULE_DEVICE_TABLE(i2c, sii9234_id);
+
+static struct i2c_driver sii9234_driver = {
+ .driver = {
+ .name = "sii9234",
+ .of_match_table = sii9234_dt_match,
+ },
+ .probe = sii9234_probe,
+ .remove = sii9234_remove,
+ .id_table = sii9234_id,
+};
+
+module_i2c_driver(sii9234_driver);
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/bridge/sil-sii8620.c b/drivers/gpu/drm/bridge/sil-sii8620.c
index 5131bfb94f06..b7eb704d0a8a 100644
--- a/drivers/gpu/drm/bridge/sil-sii8620.c
+++ b/drivers/gpu/drm/bridge/sil-sii8620.c
@@ -28,6 +28,8 @@
#include <linux/regulator/consumer.h>
#include <linux/slab.h>
+#include <media/rc-core.h>
+
#include "sil-sii8620.h"
#define SII8620_BURST_BUF_LEN 288
@@ -58,6 +60,7 @@ enum sii8620_mt_state {
struct sii8620 {
struct drm_bridge bridge;
struct device *dev;
+ struct rc_dev *rc_dev;
struct clk *clk_xtal;
struct gpio_desc *gpio_reset;
struct gpio_desc *gpio_int;
@@ -431,6 +434,16 @@ static void sii8620_mt_rap(struct sii8620 *ctx, u8 code)
sii8620_mt_msc_msg(ctx, MHL_MSC_MSG_RAP, code);
}
+static void sii8620_mt_rcpk(struct sii8620 *ctx, u8 code)
+{
+ sii8620_mt_msc_msg(ctx, MHL_MSC_MSG_RCPK, code);
+}
+
+static void sii8620_mt_rcpe(struct sii8620 *ctx, u8 code)
+{
+ sii8620_mt_msc_msg(ctx, MHL_MSC_MSG_RCPE, code);
+}
+
static void sii8620_mt_read_devcap_send(struct sii8620 *ctx,
struct sii8620_mt_msg *msg)
{
@@ -1753,6 +1766,25 @@ static void sii8620_send_features(struct sii8620 *ctx)
sii8620_write_buf(ctx, REG_MDT_XMIT_WRITE_PORT, buf, ARRAY_SIZE(buf));
}
+static bool sii8620_rcp_consume(struct sii8620 *ctx, u8 scancode)
+{
+ bool pressed = !(scancode & MHL_RCP_KEY_RELEASED_MASK);
+
+ scancode &= MHL_RCP_KEY_ID_MASK;
+
+ if (!ctx->rc_dev) {
+ dev_dbg(ctx->dev, "RCP input device not initialized\n");
+ return false;
+ }
+
+ if (pressed)
+ rc_keydown(ctx->rc_dev, RC_PROTO_CEC, scancode, 0);
+ else
+ rc_keyup(ctx->rc_dev);
+
+ return true;
+}
+
static void sii8620_msc_mr_set_int(struct sii8620 *ctx)
{
u8 ints[MHL_INT_SIZE];
@@ -1804,19 +1836,25 @@ static void sii8620_msc_mt_done(struct sii8620 *ctx)
static void sii8620_msc_mr_msc_msg(struct sii8620 *ctx)
{
- struct sii8620_mt_msg *msg = sii8620_msc_msg_first(ctx);
+ struct sii8620_mt_msg *msg;
u8 buf[2];
- if (!msg)
- return;
-
sii8620_read_buf(ctx, REG_MSC_MR_MSC_MSG_RCVD_1ST_DATA, buf, 2);
switch (buf[0]) {
case MHL_MSC_MSG_RAPK:
+ msg = sii8620_msc_msg_first(ctx);
+ if (!msg)
+ return;
msg->ret = buf[1];
ctx->mt_state = MT_STATE_DONE;
break;
+ case MHL_MSC_MSG_RCP:
+ if (!sii8620_rcp_consume(ctx, buf[1]))
+ sii8620_mt_rcpe(ctx,
+ MHL_RCPE_STATUS_INEFFECTIVE_KEY_CODE);
+ sii8620_mt_rcpk(ctx, buf[1]);
+ break;
default:
dev_err(ctx->dev, "%s message type %d,%d not supported",
__func__, buf[0], buf[1]);
@@ -2102,11 +2140,57 @@ static void sii8620_cable_in(struct sii8620 *ctx)
enable_irq(to_i2c_client(ctx->dev)->irq);
}
+static void sii8620_init_rcp_input_dev(struct sii8620 *ctx)
+{
+ struct rc_dev *rc_dev;
+ int ret;
+
+ rc_dev = rc_allocate_device(RC_DRIVER_SCANCODE);
+ if (!rc_dev) {
+ dev_err(ctx->dev, "Failed to allocate RC device\n");
+ ctx->error = -ENOMEM;
+ return;
+ }
+
+ rc_dev->input_phys = "sii8620/input0";
+ rc_dev->input_id.bustype = BUS_VIRTUAL;
+ rc_dev->map_name = RC_MAP_CEC;
+ rc_dev->allowed_protocols = RC_PROTO_BIT_CEC;
+ rc_dev->driver_name = "sii8620";
+ rc_dev->device_name = "sii8620";
+
+ ret = rc_register_device(rc_dev);
+
+ if (ret) {
+ dev_err(ctx->dev, "Failed to register RC device\n");
+ ctx->error = ret;
+ rc_free_device(ctx->rc_dev);
+ return;
+ }
+ ctx->rc_dev = rc_dev;
+}
+
static inline struct sii8620 *bridge_to_sii8620(struct drm_bridge *bridge)
{
return container_of(bridge, struct sii8620, bridge);
}
+static int sii8620_attach(struct drm_bridge *bridge)
+{
+ struct sii8620 *ctx = bridge_to_sii8620(bridge);
+
+ sii8620_init_rcp_input_dev(ctx);
+
+ return sii8620_clear_error(ctx);
+}
+
+static void sii8620_detach(struct drm_bridge *bridge)
+{
+ struct sii8620 *ctx = bridge_to_sii8620(bridge);
+
+ rc_unregister_device(ctx->rc_dev);
+}
+
static bool sii8620_mode_fixup(struct drm_bridge *bridge,
const struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
@@ -2151,6 +2235,8 @@ end:
}
static const struct drm_bridge_funcs sii8620_bridge_funcs = {
+ .attach = sii8620_attach,
+ .detach = sii8620_detach,
.mode_fixup = sii8620_mode_fixup,
};
@@ -2217,8 +2303,8 @@ static int sii8620_remove(struct i2c_client *client)
struct sii8620 *ctx = i2c_get_clientdata(client);
disable_irq(to_i2c_client(ctx->dev)->irq);
- drm_bridge_remove(&ctx->bridge);
sii8620_hw_off(ctx);
+ drm_bridge_remove(&ctx->bridge);
return 0;
}
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c b/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c
index f4f633a0dffa..d9cca4fd66ec 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c
@@ -221,7 +221,6 @@ struct dw_mipi_dsi {
struct drm_bridge bridge;
struct mipi_dsi_host dsi_host;
struct drm_bridge *panel_bridge;
- bool is_panel_bridge;
struct device *dev;
void __iomem *base;
@@ -297,7 +296,6 @@ static int dw_mipi_dsi_host_attach(struct mipi_dsi_host *host,
bridge = drm_panel_bridge_add(panel, DRM_MODE_CONNECTOR_DSI);
if (IS_ERR(bridge))
return PTR_ERR(bridge);
- dsi->is_panel_bridge = true;
}
dsi->panel_bridge = bridge;
@@ -312,8 +310,7 @@ static int dw_mipi_dsi_host_detach(struct mipi_dsi_host *host,
{
struct dw_mipi_dsi *dsi = host_to_dsi(host);
- if (dsi->is_panel_bridge)
- drm_panel_bridge_remove(dsi->panel_bridge);
+ drm_of_panel_bridge_remove(host->dev->of_node, 1, 0);
drm_bridge_remove(&dsi->bridge);