]> asedeno.scripts.mit.edu Git - linux.git/blobdiff - drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
Merge tag 'sunxi-drm-for-4.14' of https://git.kernel.org/pub/scm/linux/kernel/git...
[linux.git] / drivers / gpu / drm / sun4i / sun4i_hdmi_enc.c
index f5d0d6bd1084472b0cb255235abba5f87eb9c717..9ea6cd5a1370d92e6eb78c864641986bffbc0086 100644 (file)
@@ -29,8 +29,6 @@
 #include "sun4i_hdmi.h"
 #include "sun4i_tcon.h"
 
-#define DDC_SEGMENT_ADDR       0x30
-
 static inline struct sun4i_hdmi *
 drm_encoder_to_sun4i_hdmi(struct drm_encoder *encoder)
 {
@@ -184,93 +182,13 @@ static const struct drm_encoder_funcs sun4i_hdmi_funcs = {
        .destroy        = drm_encoder_cleanup,
 };
 
-static int sun4i_hdmi_read_sub_block(struct sun4i_hdmi *hdmi,
-                                    unsigned int blk, unsigned int offset,
-                                    u8 *buf, unsigned int count)
-{
-       unsigned long reg;
-       int i;
-
-       reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
-       reg &= ~SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK;
-       writel(reg | SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ,
-              hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
-
-       writel(SUN4I_HDMI_DDC_ADDR_SEGMENT(offset >> 8) |
-              SUN4I_HDMI_DDC_ADDR_EDDC(DDC_SEGMENT_ADDR << 1) |
-              SUN4I_HDMI_DDC_ADDR_OFFSET(offset) |
-              SUN4I_HDMI_DDC_ADDR_SLAVE(DDC_ADDR),
-              hdmi->base + SUN4I_HDMI_DDC_ADDR_REG);
-
-       reg = readl(hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG);
-       writel(reg | SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR,
-              hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG);
-
-       writel(count, hdmi->base + SUN4I_HDMI_DDC_BYTE_COUNT_REG);
-       writel(SUN4I_HDMI_DDC_CMD_EXPLICIT_EDDC_READ,
-              hdmi->base + SUN4I_HDMI_DDC_CMD_REG);
-
-       reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
-       writel(reg | SUN4I_HDMI_DDC_CTRL_START_CMD,
-              hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
-
-       if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG, reg,
-                              !(reg & SUN4I_HDMI_DDC_CTRL_START_CMD),
-                              100, 100000))
-               return -EIO;
-
-       for (i = 0; i < count; i++)
-               buf[i] = readb(hdmi->base + SUN4I_HDMI_DDC_FIFO_DATA_REG);
-
-       return 0;
-}
-
-static int sun4i_hdmi_read_edid_block(void *data, u8 *buf, unsigned int blk,
-                                     size_t length)
-{
-       struct sun4i_hdmi *hdmi = data;
-       int retry = 2, i;
-
-       do {
-               for (i = 0; i < length; i += SUN4I_HDMI_DDC_FIFO_SIZE) {
-                       unsigned char offset = blk * EDID_LENGTH + i;
-                       unsigned int count = min((unsigned int)SUN4I_HDMI_DDC_FIFO_SIZE,
-                                                length - i);
-                       int ret;
-
-                       ret = sun4i_hdmi_read_sub_block(hdmi, blk, offset,
-                                                       buf + i, count);
-                       if (ret)
-                               return ret;
-               }
-       } while (!drm_edid_block_valid(buf, blk, true, NULL) && (retry--));
-
-       return 0;
-}
-
 static int sun4i_hdmi_get_modes(struct drm_connector *connector)
 {
        struct sun4i_hdmi *hdmi = drm_connector_to_sun4i_hdmi(connector);
-       unsigned long reg;
        struct edid *edid;
        int ret;
 
-       /* Reset i2c controller */
-       writel(SUN4I_HDMI_DDC_CTRL_ENABLE | SUN4I_HDMI_DDC_CTRL_RESET,
-              hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
-       if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG, reg,
-                              !(reg & SUN4I_HDMI_DDC_CTRL_RESET),
-                              100, 2000))
-               return -EIO;
-
-       writel(SUN4I_HDMI_DDC_LINE_CTRL_SDA_ENABLE |
-              SUN4I_HDMI_DDC_LINE_CTRL_SCL_ENABLE,
-              hdmi->base + SUN4I_HDMI_DDC_LINE_CTRL_REG);
-
-       clk_prepare_enable(hdmi->ddc_clk);
-       clk_set_rate(hdmi->ddc_clk, 100000);
-
-       edid = drm_do_get_edid(connector, sun4i_hdmi_read_edid_block, hdmi);
+       edid = drm_get_edid(connector, hdmi->i2c);
        if (!edid)
                return 0;
 
@@ -279,11 +197,10 @@ static int sun4i_hdmi_get_modes(struct drm_connector *connector)
                         hdmi->hdmi_monitor ? "an HDMI" : "a DVI");
 
        drm_mode_connector_update_edid_property(connector, edid);
+       cec_s_phys_addr_from_edid(hdmi->cec_adap, edid);
        ret = drm_add_edid_modes(connector, edid);
        kfree(edid);
 
-       clk_disable_unprepare(hdmi->ddc_clk);
-
        return ret;
 }
 
@@ -299,8 +216,10 @@ sun4i_hdmi_connector_detect(struct drm_connector *connector, bool force)
 
        if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_HPD_REG, reg,
                               reg & SUN4I_HDMI_HPD_HIGH,
-                              0, 500000))
+                              0, 500000)) {
+               cec_phys_addr_invalidate(hdmi->cec_adap);
                return connector_status_disconnected;
+       }
 
        return connector_status_connected;
 }
@@ -314,6 +233,40 @@ static const struct drm_connector_funcs sun4i_hdmi_connector_funcs = {
        .atomic_destroy_state   = drm_atomic_helper_connector_destroy_state,
 };
 
+#ifdef CONFIG_DRM_SUN4I_HDMI_CEC
+static bool sun4i_hdmi_cec_pin_read(struct cec_adapter *adap)
+{
+       struct sun4i_hdmi *hdmi = cec_get_drvdata(adap);
+
+       return readl(hdmi->base + SUN4I_HDMI_CEC) & SUN4I_HDMI_CEC_RX;
+}
+
+static void sun4i_hdmi_cec_pin_low(struct cec_adapter *adap)
+{
+       struct sun4i_hdmi *hdmi = cec_get_drvdata(adap);
+
+       /* Start driving the CEC pin low */
+       writel(SUN4I_HDMI_CEC_ENABLE, hdmi->base + SUN4I_HDMI_CEC);
+}
+
+static void sun4i_hdmi_cec_pin_high(struct cec_adapter *adap)
+{
+       struct sun4i_hdmi *hdmi = cec_get_drvdata(adap);
+
+       /*
+        * Stop driving the CEC pin, the pull up will take over
+        * unless another CEC device is driving the pin low.
+        */
+       writel(0, hdmi->base + SUN4I_HDMI_CEC);
+}
+
+static const struct cec_pin_ops sun4i_hdmi_cec_pin_ops = {
+       .read = sun4i_hdmi_cec_pin_read,
+       .low = sun4i_hdmi_cec_pin_low,
+       .high = sun4i_hdmi_cec_pin_high,
+};
+#endif
+
 static int sun4i_hdmi_bind(struct device *dev, struct device *master,
                           void *data)
 {
@@ -406,9 +359,9 @@ static int sun4i_hdmi_bind(struct device *dev, struct device *master,
                SUN4I_HDMI_PLL_CTRL_PLL_EN;
        writel(reg, hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
 
-       ret = sun4i_ddc_create(hdmi, hdmi->tmds_clk);
+       ret = sun4i_hdmi_i2c_create(dev, hdmi);
        if (ret) {
-               dev_err(dev, "Couldn't create the DDC clock\n");
+               dev_err(dev, "Couldn't create the HDMI I2C adapter\n");
                return ret;
        }
 
@@ -421,13 +374,26 @@ static int sun4i_hdmi_bind(struct device *dev, struct device *master,
                               NULL);
        if (ret) {
                dev_err(dev, "Couldn't initialise the HDMI encoder\n");
-               return ret;
+               goto err_del_i2c_adapter;
        }
 
        hdmi->encoder.possible_crtcs = drm_of_find_possible_crtcs(drm,
                                                                  dev->of_node);
-       if (!hdmi->encoder.possible_crtcs)
-               return -EPROBE_DEFER;
+       if (!hdmi->encoder.possible_crtcs) {
+               ret = -EPROBE_DEFER;
+               goto err_del_i2c_adapter;
+       }
+
+#ifdef CONFIG_DRM_SUN4I_HDMI_CEC
+       hdmi->cec_adap = cec_pin_allocate_adapter(&sun4i_hdmi_cec_pin_ops,
+               hdmi, "sun4i", CEC_CAP_TRANSMIT | CEC_CAP_LOG_ADDRS |
+               CEC_CAP_PASSTHROUGH | CEC_CAP_RC);
+       ret = PTR_ERR_OR_ZERO(hdmi->cec_adap);
+       if (ret < 0)
+               goto err_cleanup_connector;
+       writel(readl(hdmi->base + SUN4I_HDMI_CEC) & ~SUN4I_HDMI_CEC_TX,
+              hdmi->base + SUN4I_HDMI_CEC);
+#endif
 
        drm_connector_helper_add(&hdmi->connector,
                                 &sun4i_hdmi_connector_helper_funcs);
@@ -444,12 +410,18 @@ static int sun4i_hdmi_bind(struct device *dev, struct device *master,
        hdmi->connector.polled = DRM_CONNECTOR_POLL_CONNECT |
                DRM_CONNECTOR_POLL_DISCONNECT;
 
+       ret = cec_register_adapter(hdmi->cec_adap, dev);
+       if (ret < 0)
+               goto err_cleanup_connector;
        drm_mode_connector_attach_encoder(&hdmi->connector, &hdmi->encoder);
 
        return 0;
 
 err_cleanup_connector:
+       cec_delete_adapter(hdmi->cec_adap);
        drm_encoder_cleanup(&hdmi->encoder);
+err_del_i2c_adapter:
+       i2c_del_adapter(hdmi->i2c);
        return ret;
 }
 
@@ -458,8 +430,10 @@ static void sun4i_hdmi_unbind(struct device *dev, struct device *master,
 {
        struct sun4i_hdmi *hdmi = dev_get_drvdata(dev);
 
+       cec_unregister_adapter(hdmi->cec_adap);
        drm_connector_cleanup(&hdmi->connector);
        drm_encoder_cleanup(&hdmi->encoder);
+       i2c_del_adapter(hdmi->i2c);
 }
 
 static const struct component_ops sun4i_hdmi_ops = {