Merge tag 'for-f2fs-4.8' of git://git.kernel.org/pub/scm/linux/kernel/git/jaegeuk...
[cascardo/linux.git] / drivers / media / i2c / adv7511.c
index 39271c3..6d7cad5 100644 (file)
@@ -33,6 +33,7 @@
 #include <media/v4l2-ctrls.h>
 #include <media/v4l2-dv-timings.h>
 #include <media/i2c/adv7511.h>
+#include <media/cec.h>
 
 static int debug;
 module_param(debug, int, 0644);
@@ -59,6 +60,8 @@ MODULE_LICENSE("GPL v2");
 #define ADV7511_MIN_PIXELCLOCK 20000000
 #define ADV7511_MAX_PIXELCLOCK 225000000
 
+#define ADV7511_MAX_ADDRS (3)
+
 /*
 **********************************************************************
 *
@@ -90,12 +93,20 @@ struct adv7511_state {
        struct v4l2_ctrl_handler hdl;
        int chip_revision;
        u8 i2c_edid_addr;
-       u8 i2c_cec_addr;
        u8 i2c_pktmem_addr;
+       u8 i2c_cec_addr;
+
+       struct i2c_client *i2c_cec;
+       struct cec_adapter *cec_adap;
+       u8   cec_addr[ADV7511_MAX_ADDRS];
+       u8   cec_valid_addrs;
+       bool cec_enabled_adap;
+
        /* Is the adv7511 powered on? */
        bool power_on;
        /* Did we receive hotplug and rx-sense signals? */
        bool have_monitor;
+       bool enabled_irq;
        /* timings from s_dv_timings */
        struct v4l2_dv_timings dv_timings;
        u32 fmt_code;
@@ -227,7 +238,7 @@ static int adv_smbus_read_i2c_block_data(struct i2c_client *client,
        return ret;
 }
 
-static inline void adv7511_edid_rd(struct v4l2_subdev *sd, u16 len, u8 *buf)
+static void adv7511_edid_rd(struct v4l2_subdev *sd, uint16_t len, uint8_t *buf)
 {
        struct adv7511_state *state = get_adv7511_state(sd);
        int i;
@@ -242,6 +253,34 @@ static inline void adv7511_edid_rd(struct v4l2_subdev *sd, u16 len, u8 *buf)
                v4l2_err(sd, "%s: i2c read error\n", __func__);
 }
 
+static inline int adv7511_cec_read(struct v4l2_subdev *sd, u8 reg)
+{
+       struct adv7511_state *state = get_adv7511_state(sd);
+
+       return i2c_smbus_read_byte_data(state->i2c_cec, reg);
+}
+
+static int adv7511_cec_write(struct v4l2_subdev *sd, u8 reg, u8 val)
+{
+       struct adv7511_state *state = get_adv7511_state(sd);
+       int ret;
+       int i;
+
+       for (i = 0; i < 3; i++) {
+               ret = i2c_smbus_write_byte_data(state->i2c_cec, reg, val);
+               if (ret == 0)
+                       return 0;
+       }
+       v4l2_err(sd, "%s: I2C Write Problem\n", __func__);
+       return ret;
+}
+
+static inline int adv7511_cec_write_and_or(struct v4l2_subdev *sd, u8 reg, u8 mask,
+                                  u8 val)
+{
+       return adv7511_cec_write(sd, reg, (adv7511_cec_read(sd, reg) & mask) | val);
+}
+
 static int adv7511_pktmem_rd(struct v4l2_subdev *sd, u8 reg)
 {
        struct adv7511_state *state = get_adv7511_state(sd);
@@ -343,28 +382,20 @@ static void adv7511_csc_rgb_full2limit(struct v4l2_subdev *sd, bool enable)
        }
 }
 
-static void adv7511_set_IT_content_AVI_InfoFrame(struct v4l2_subdev *sd)
+static void adv7511_set_rgb_quantization_mode(struct v4l2_subdev *sd, struct v4l2_ctrl *ctrl)
 {
        struct adv7511_state *state = get_adv7511_state(sd);
-       if (state->dv_timings.bt.flags & V4L2_DV_FL_IS_CE_VIDEO) {
-               /* CE format, not IT  */
-               adv7511_wr_and_or(sd, 0x57, 0x7f, 0x00);
-       } else {
-               /* IT format */
-               adv7511_wr_and_or(sd, 0x57, 0x7f, 0x80);
+
+       /* Only makes sense for RGB formats */
+       if (state->fmt_code != MEDIA_BUS_FMT_RGB888_1X24) {
+               /* so just keep quantization */
+               adv7511_csc_rgb_full2limit(sd, false);
+               return;
        }
-}
 
-static int adv7511_set_rgb_quantization_mode(struct v4l2_subdev *sd, struct v4l2_ctrl *ctrl)
-{
        switch (ctrl->val) {
-       default:
-               return -EINVAL;
-               break;
-       case V4L2_DV_RGB_RANGE_AUTO: {
+       case V4L2_DV_RGB_RANGE_AUTO:
                /* automatic */
-               struct adv7511_state *state = get_adv7511_state(sd);
-
                if (state->dv_timings.bt.flags & V4L2_DV_FL_IS_CE_VIDEO) {
                        /* CE format, RGB limited range (16-235) */
                        adv7511_csc_rgb_full2limit(sd, true);
@@ -372,7 +403,6 @@ static int adv7511_set_rgb_quantization_mode(struct v4l2_subdev *sd, struct v4l2
                        /* not CE format, RGB full range (0-255) */
                        adv7511_csc_rgb_full2limit(sd, false);
                }
-       }
                break;
        case V4L2_DV_RGB_RANGE_LIMITED:
                /* RGB limited range (16-235) */
@@ -383,7 +413,6 @@ static int adv7511_set_rgb_quantization_mode(struct v4l2_subdev *sd, struct v4l2
                adv7511_csc_rgb_full2limit(sd, false);
                break;
        }
-       return 0;
 }
 
 /* ------------------------------ CTRL OPS ------------------------------ */
@@ -400,8 +429,10 @@ static int adv7511_s_ctrl(struct v4l2_ctrl *ctrl)
                adv7511_wr_and_or(sd, 0xaf, 0xfd, ctrl->val == V4L2_DV_TX_MODE_HDMI ? 0x02 : 0x00);
                return 0;
        }
-       if (state->rgb_quantization_range_ctrl == ctrl)
-               return adv7511_set_rgb_quantization_mode(sd, ctrl);
+       if (state->rgb_quantization_range_ctrl == ctrl) {
+               adv7511_set_rgb_quantization_mode(sd, ctrl);
+               return 0;
+       }
        if (state->content_type_ctrl == ctrl) {
                u8 itc, cn;
 
@@ -425,16 +456,28 @@ static const struct v4l2_ctrl_ops adv7511_ctrl_ops = {
 #ifdef CONFIG_VIDEO_ADV_DEBUG
 static void adv7511_inv_register(struct v4l2_subdev *sd)
 {
+       struct adv7511_state *state = get_adv7511_state(sd);
+
        v4l2_info(sd, "0x000-0x0ff: Main Map\n");
+       if (state->i2c_cec)
+               v4l2_info(sd, "0x100-0x1ff: CEC Map\n");
 }
 
 static int adv7511_g_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg)
 {
+       struct adv7511_state *state = get_adv7511_state(sd);
+
        reg->size = 1;
        switch (reg->reg >> 8) {
        case 0:
                reg->val = adv7511_rd(sd, reg->reg & 0xff);
                break;
+       case 1:
+               if (state->i2c_cec) {
+                       reg->val = adv7511_cec_read(sd, reg->reg & 0xff);
+                       break;
+               }
+               /* fall through */
        default:
                v4l2_info(sd, "Register %03llx not supported\n", reg->reg);
                adv7511_inv_register(sd);
@@ -445,10 +488,18 @@ static int adv7511_g_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *
 
 static int adv7511_s_register(struct v4l2_subdev *sd, const struct v4l2_dbg_register *reg)
 {
+       struct adv7511_state *state = get_adv7511_state(sd);
+
        switch (reg->reg >> 8) {
        case 0:
                adv7511_wr(sd, reg->reg & 0xff, reg->val & 0xff);
                break;
+       case 1:
+               if (state->i2c_cec) {
+                       adv7511_cec_write(sd, reg->reg & 0xff, reg->val & 0xff);
+                       break;
+               }
+               /* fall through */
        default:
                v4l2_info(sd, "Register %03llx not supported\n", reg->reg);
                adv7511_inv_register(sd);
@@ -536,6 +587,7 @@ static int adv7511_log_status(struct v4l2_subdev *sd)
 {
        struct adv7511_state *state = get_adv7511_state(sd);
        struct adv7511_state_edid *edid = &state->edid;
+       int i;
 
        static const char * const states[] = {
                "in reset",
@@ -605,7 +657,23 @@ static int adv7511_log_status(struct v4l2_subdev *sd)
        else
                v4l2_info(sd, "no timings set\n");
        v4l2_info(sd, "i2c edid addr: 0x%x\n", state->i2c_edid_addr);
+
+       if (state->i2c_cec == NULL)
+               return 0;
+
        v4l2_info(sd, "i2c cec addr: 0x%x\n", state->i2c_cec_addr);
+
+       v4l2_info(sd, "CEC: %s\n", state->cec_enabled_adap ?
+                       "enabled" : "disabled");
+       if (state->cec_enabled_adap) {
+               for (i = 0; i < ADV7511_MAX_ADDRS; i++) {
+                       bool is_valid = state->cec_valid_addrs & (1 << i);
+
+                       if (is_valid)
+                               v4l2_info(sd, "CEC Logical Address: 0x%x\n",
+                                         state->cec_addr[i]);
+               }
+       }
        v4l2_info(sd, "i2c pktmem addr: 0x%x\n", state->i2c_pktmem_addr);
        return 0;
 }
@@ -663,15 +731,197 @@ static int adv7511_s_power(struct v4l2_subdev *sd, int on)
        return true;
 }
 
+#if IS_ENABLED(CONFIG_VIDEO_ADV7511_CEC)
+static int adv7511_cec_adap_enable(struct cec_adapter *adap, bool enable)
+{
+       struct adv7511_state *state = adap->priv;
+       struct v4l2_subdev *sd = &state->sd;
+
+       if (state->i2c_cec == NULL)
+               return -EIO;
+
+       if (!state->cec_enabled_adap && enable) {
+               /* power up cec section */
+               adv7511_cec_write_and_or(sd, 0x4e, 0xfc, 0x01);
+               /* legacy mode and clear all rx buffers */
+               adv7511_cec_write(sd, 0x4a, 0x07);
+               adv7511_cec_write(sd, 0x4a, 0);
+               adv7511_cec_write_and_or(sd, 0x11, 0xfe, 0); /* initially disable tx */
+               /* enabled irqs: */
+               /* tx: ready */
+               /* tx: arbitration lost */
+               /* tx: retry timeout */
+               /* rx: ready 1 */
+               if (state->enabled_irq)
+                       adv7511_wr_and_or(sd, 0x95, 0xc0, 0x39);
+       } else if (state->cec_enabled_adap && !enable) {
+               if (state->enabled_irq)
+                       adv7511_wr_and_or(sd, 0x95, 0xc0, 0x00);
+               /* disable address mask 1-3 */
+               adv7511_cec_write_and_or(sd, 0x4b, 0x8f, 0x00);
+               /* power down cec section */
+               adv7511_cec_write_and_or(sd, 0x4e, 0xfc, 0x00);
+               state->cec_valid_addrs = 0;
+       }
+       state->cec_enabled_adap = enable;
+       return 0;
+}
+
+static int adv7511_cec_adap_log_addr(struct cec_adapter *adap, u8 addr)
+{
+       struct adv7511_state *state = adap->priv;
+       struct v4l2_subdev *sd = &state->sd;
+       unsigned int i, free_idx = ADV7511_MAX_ADDRS;
+
+       if (!state->cec_enabled_adap)
+               return addr == CEC_LOG_ADDR_INVALID ? 0 : -EIO;
+
+       if (addr == CEC_LOG_ADDR_INVALID) {
+               adv7511_cec_write_and_or(sd, 0x4b, 0x8f, 0);
+               state->cec_valid_addrs = 0;
+               return 0;
+       }
+
+       for (i = 0; i < ADV7511_MAX_ADDRS; i++) {
+               bool is_valid = state->cec_valid_addrs & (1 << i);
+
+               if (free_idx == ADV7511_MAX_ADDRS && !is_valid)
+                       free_idx = i;
+               if (is_valid && state->cec_addr[i] == addr)
+                       return 0;
+       }
+       if (i == ADV7511_MAX_ADDRS) {
+               i = free_idx;
+               if (i == ADV7511_MAX_ADDRS)
+                       return -ENXIO;
+       }
+       state->cec_addr[i] = addr;
+       state->cec_valid_addrs |= 1 << i;
+
+       switch (i) {
+       case 0:
+               /* enable address mask 0 */
+               adv7511_cec_write_and_or(sd, 0x4b, 0xef, 0x10);
+               /* set address for mask 0 */
+               adv7511_cec_write_and_or(sd, 0x4c, 0xf0, addr);
+               break;
+       case 1:
+               /* enable address mask 1 */
+               adv7511_cec_write_and_or(sd, 0x4b, 0xdf, 0x20);
+               /* set address for mask 1 */
+               adv7511_cec_write_and_or(sd, 0x4c, 0x0f, addr << 4);
+               break;
+       case 2:
+               /* enable address mask 2 */
+               adv7511_cec_write_and_or(sd, 0x4b, 0xbf, 0x40);
+               /* set address for mask 1 */
+               adv7511_cec_write_and_or(sd, 0x4d, 0xf0, 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_state *state = adap->priv;
+       struct v4l2_subdev *sd = &state->sd;
+       u8 len = msg->len;
+       unsigned int i;
+
+       v4l2_dbg(1, debug, sd, "%s: len %d\n", __func__, len);
+
+       if (len > 16) {
+               v4l2_err(sd, "%s: len exceeded 16 (%d)\n", __func__, len);
+               return -EINVAL;
+       }
+
+       /*
+        * 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.
+        */
+       adv7511_cec_write_and_or(sd, 0x12, ~0x70, max(1, attempts - 1) << 4);
+
+       /* blocking, clear cec tx irq status */
+       adv7511_wr_and_or(sd, 0x97, 0xc7, 0x38);
+
+       /* write data */
+       for (i = 0; i < len; i++)
+               adv7511_cec_write(sd, i, msg->msg[i]);
+
+       /* set length (data + header) */
+       adv7511_cec_write(sd, 0x10, len);
+       /* start transmit, enable tx */
+       adv7511_cec_write(sd, 0x11, 0x01);
+       return 0;
+}
+
+static void adv_cec_tx_raw_status(struct v4l2_subdev *sd, u8 tx_raw_status)
+{
+       struct adv7511_state *state = get_adv7511_state(sd);
+
+       if ((adv7511_cec_read(sd, 0x11) & 0x01) == 0) {
+               v4l2_dbg(1, debug, sd, "%s: tx raw: tx disabled\n", __func__);
+               return;
+       }
+
+       if (tx_raw_status & 0x10) {
+               v4l2_dbg(1, debug, sd,
+                        "%s: tx raw: arbitration lost\n", __func__);
+               cec_transmit_done(state->cec_adap, CEC_TX_STATUS_ARB_LOST,
+                                 1, 0, 0, 0);
+               return;
+       }
+       if (tx_raw_status & 0x08) {
+               u8 status;
+               u8 nack_cnt;
+               u8 low_drive_cnt;
+
+               v4l2_dbg(1, debug, sd, "%s: tx raw: retry failed\n", __func__);
+               /*
+                * We set this status bit since this hardware performs
+                * retransmissions.
+                */
+               status = CEC_TX_STATUS_MAX_RETRIES;
+               nack_cnt = adv7511_cec_read(sd, 0x14) & 0xf;
+               if (nack_cnt)
+                       status |= CEC_TX_STATUS_NACK;
+               low_drive_cnt = adv7511_cec_read(sd, 0x14) >> 4;
+               if (low_drive_cnt)
+                       status |= CEC_TX_STATUS_LOW_DRIVE;
+               cec_transmit_done(state->cec_adap, status,
+                                 0, nack_cnt, low_drive_cnt, 0);
+               return;
+       }
+       if (tx_raw_status & 0x20) {
+               v4l2_dbg(1, debug, sd, "%s: tx raw: ready ok\n", __func__);
+               cec_transmit_done(state->cec_adap, CEC_TX_STATUS_OK, 0, 0, 0, 0);
+               return;
+       }
+}
+
+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,
+};
+#endif
+
 /* Enable interrupts */
 static void adv7511_set_isr(struct v4l2_subdev *sd, bool enable)
 {
+       struct adv7511_state *state = get_adv7511_state(sd);
        u8 irqs = MASK_ADV7511_HPD_INT | MASK_ADV7511_MSEN_INT;
        u8 irqs_rd;
        int retries = 100;
 
        v4l2_dbg(2, debug, sd, "%s: %s\n", __func__, enable ? "enable" : "disable");
 
+       if (state->enabled_irq == enable)
+               return;
+       state->enabled_irq = enable;
+
        /* The datasheet says that the EDID ready interrupt should be
           disabled if there is no hotplug. */
        if (!enable)
@@ -679,6 +929,9 @@ static void adv7511_set_isr(struct v4l2_subdev *sd, bool enable)
        else if (adv7511_have_hotplug(sd))
                irqs |= MASK_ADV7511_EDID_RDY_INT;
 
+       adv7511_wr_and_or(sd, 0x95, 0xc0,
+                         (state->cec_enabled_adap && enable) ? 0x39 : 0x00);
+
        /*
         * This i2c write can fail (approx. 1 in 1000 writes). But it
         * is essential that this register is correct, so retry it
@@ -701,20 +954,53 @@ static void adv7511_set_isr(struct v4l2_subdev *sd, bool enable)
 static int adv7511_isr(struct v4l2_subdev *sd, u32 status, bool *handled)
 {
        u8 irq_status;
+       u8 cec_irq;
 
        /* disable interrupts to prevent a race condition */
        adv7511_set_isr(sd, false);
        irq_status = adv7511_rd(sd, 0x96);
+       cec_irq = adv7511_rd(sd, 0x97);
        /* clear detected interrupts */
        adv7511_wr(sd, 0x96, irq_status);
+       adv7511_wr(sd, 0x97, cec_irq);
 
-       v4l2_dbg(1, debug, sd, "%s: irq 0x%x\n", __func__, irq_status);
+       v4l2_dbg(1, debug, sd, "%s: irq 0x%x, cec-irq 0x%x\n", __func__,
+                irq_status, cec_irq);
 
        if (irq_status & (MASK_ADV7511_HPD_INT | MASK_ADV7511_MSEN_INT))
                adv7511_check_monitor_present_status(sd);
        if (irq_status & MASK_ADV7511_EDID_RDY_INT)
                adv7511_check_edid_status(sd);
 
+#if IS_ENABLED(CONFIG_VIDEO_ADV7511_CEC)
+       if (cec_irq & 0x38)
+               adv_cec_tx_raw_status(sd, cec_irq);
+
+       if (cec_irq & 1) {
+               struct adv7511_state *state = get_adv7511_state(sd);
+               struct cec_msg msg;
+
+               msg.len = adv7511_cec_read(sd, 0x25) & 0x1f;
+
+               v4l2_dbg(1, debug, sd, "%s: cec msg len %d\n", __func__,
+                        msg.len);
+
+               if (msg.len > 16)
+                       msg.len = 16;
+
+               if (msg.len) {
+                       u8 i;
+
+                       for (i = 0; i < msg.len; i++)
+                               msg.msg[i] = adv7511_cec_read(sd, i + 0x15);
+
+                       adv7511_cec_write(sd, 0x4a, 1); /* toggle to re-enable rx 1 */
+                       adv7511_cec_write(sd, 0x4a, 0);
+                       cec_received_msg(state->cec_adap, &msg);
+               }
+       }
+#endif
+
        /* enable interrupts */
        adv7511_set_isr(sd, true);
 
@@ -771,12 +1057,14 @@ static int adv7511_s_dv_timings(struct v4l2_subdev *sd,
        /* save timings */
        state->dv_timings = *timings;
 
+       /* set h/vsync polarities */
+       adv7511_wr_and_or(sd, 0x17, 0x9f,
+               ((timings->bt.polarities & V4L2_DV_VSYNC_POS_POL) ? 0 : 0x40) |
+               ((timings->bt.polarities & V4L2_DV_HSYNC_POS_POL) ? 0 : 0x20));
+
        /* update quantization range based on new dv_timings */
        adv7511_set_rgb_quantization_mode(sd, state->rgb_quantization_range_ctrl);
 
-       /* update AVI infoframe */
-       adv7511_set_IT_content_AVI_InfoFrame(sd);
-
        return 0;
 }
 
@@ -956,8 +1244,6 @@ static int adv7511_enum_mbus_code(struct v4l2_subdev *sd,
 static void adv7511_fill_format(struct adv7511_state *state,
                                struct v4l2_mbus_framefmt *format)
 {
-       memset(format, 0, sizeof(*format));
-
        format->width = state->dv_timings.bt.width;
        format->height = state->dv_timings.bt.height;
        format->field = V4L2_FIELD_NONE;
@@ -972,6 +1258,7 @@ static int adv7511_get_fmt(struct v4l2_subdev *sd,
        if (format->pad != 0)
                return -EINVAL;
 
+       memset(&format->format, 0, sizeof(format->format));
        adv7511_fill_format(state, &format->format);
 
        if (format->which == V4L2_SUBDEV_FORMAT_TRY) {
@@ -1132,6 +1419,7 @@ static int adv7511_set_fmt(struct v4l2_subdev *sd,
        adv7511_wr_and_or(sd, 0x57, 0x83, (ec << 4) | (q << 2) | (itc << 7));
        adv7511_wr_and_or(sd, 0x59, 0x0f, (yq << 6) | (cn << 4));
        adv7511_wr_and_or(sd, 0x4a, 0xff, 1);
+       adv7511_set_rgb_quantization_mode(sd, state->rgb_quantization_range_ctrl);
 
        return 0;
 }
@@ -1183,6 +1471,8 @@ static void adv7511_notify_no_edid(struct v4l2_subdev *sd)
        /* We failed to read the EDID, so send an event for this. */
        ed.present = false;
        ed.segment = adv7511_rd(sd, 0xc4);
+       ed.phys_addr = CEC_PHYS_ADDR_INVALID;
+       cec_s_phys_addr(state->cec_adap, ed.phys_addr, false);
        v4l2_subdev_notify(sd, ADV7511_EDID_DETECT, (void *)&ed);
        v4l2_ctrl_s_ctrl(state->have_edid0_ctrl, 0x0);
 }
@@ -1406,13 +1696,16 @@ static bool adv7511_check_edid_status(struct v4l2_subdev *sd)
 
                v4l2_dbg(1, debug, sd, "%s: edid complete with %d segment(s)\n", __func__, state->edid.segments);
                state->edid.complete = true;
-
+               ed.phys_addr = cec_get_edid_phys_addr(state->edid.data,
+                                                     state->edid.segments * 256,
+                                                     NULL);
                /* report when we have all segments
                   but report only for segment 0
                 */
                ed.present = true;
                ed.segment = 0;
                state->edid_detect_counter++;
+               cec_s_phys_addr(state->cec_adap, ed.phys_addr, false);
                v4l2_subdev_notify(sd, ADV7511_EDID_DETECT, (void *)&ed);
                return ed.present;
        }
@@ -1420,17 +1713,43 @@ static bool adv7511_check_edid_status(struct v4l2_subdev *sd)
        return false;
 }
 
+static int adv7511_registered(struct v4l2_subdev *sd)
+{
+       struct adv7511_state *state = get_adv7511_state(sd);
+       int err;
+
+       err = cec_register_adapter(state->cec_adap);
+       if (err)
+               cec_delete_adapter(state->cec_adap);
+       return err;
+}
+
+static void adv7511_unregistered(struct v4l2_subdev *sd)
+{
+       struct adv7511_state *state = get_adv7511_state(sd);
+
+       cec_unregister_adapter(state->cec_adap);
+}
+
+static const struct v4l2_subdev_internal_ops adv7511_int_ops = {
+       .registered = adv7511_registered,
+       .unregistered = adv7511_unregistered,
+};
+
 /* ----------------------------------------------------------------------- */
 /* Setup ADV7511 */
 static void adv7511_init_setup(struct v4l2_subdev *sd)
 {
        struct adv7511_state *state = get_adv7511_state(sd);
        struct adv7511_state_edid *edid = &state->edid;
+       u32 cec_clk = state->pdata.cec_clk;
+       u8 ratio;
 
        v4l2_dbg(1, debug, sd, "%s\n", __func__);
 
        /* clear all interrupts */
        adv7511_wr(sd, 0x96, 0xff);
+       adv7511_wr(sd, 0x97, 0xff);
        /*
         * Stop HPD from resetting a lot of registers.
         * It might leave the chip in a partly un-initialized state,
@@ -1442,6 +1761,25 @@ static void adv7511_init_setup(struct v4l2_subdev *sd)
        adv7511_set_isr(sd, false);
        adv7511_s_stream(sd, false);
        adv7511_s_audio_stream(sd, false);
+
+       if (state->i2c_cec == NULL)
+               return;
+
+       v4l2_dbg(1, debug, sd, "%s: cec_clk %d\n", __func__, cec_clk);
+
+       /* cec soft reset */
+       adv7511_cec_write(sd, 0x50, 0x01);
+       adv7511_cec_write(sd, 0x50, 0x00);
+
+       /* legacy mode */
+       adv7511_cec_write(sd, 0x4a, 0x00);
+
+       if (cec_clk % 750000 != 0)
+               v4l2_err(sd, "%s: cec_clk %d, not multiple of 750 Khz\n",
+                        __func__, cec_clk);
+
+       ratio = (cec_clk / 750000) - 1;
+       adv7511_cec_write(sd, 0x4e, ratio << 2);
 }
 
 static int adv7511_probe(struct i2c_client *client, const struct i2c_device_id *id)
@@ -1476,6 +1814,7 @@ static int adv7511_probe(struct i2c_client *client, const struct i2c_device_id *
                         client->addr << 1);
 
        v4l2_i2c_subdev_init(sd, client, &adv7511_ops);
+       sd->internal_ops = &adv7511_int_ops;
 
        hdl = &state->hdl;
        v4l2_ctrl_handler_init(hdl, 10);
@@ -1516,26 +1855,47 @@ static int adv7511_probe(struct i2c_client *client, const struct i2c_device_id *
        chip_id[0] = adv7511_rd(sd, 0xf5);
        chip_id[1] = adv7511_rd(sd, 0xf6);
        if (chip_id[0] != 0x75 || chip_id[1] != 0x11) {
-               v4l2_err(sd, "chip_id != 0x7511, read 0x%02x%02x\n", chip_id[0], chip_id[1]);
+               v4l2_err(sd, "chip_id != 0x7511, read 0x%02x%02x\n", chip_id[0],
+                        chip_id[1]);
                err = -EIO;
                goto err_entity;
        }
 
-       state->i2c_edid = i2c_new_dummy(client->adapter, state->i2c_edid_addr >> 1);
+       state->i2c_edid = i2c_new_dummy(client->adapter,
+                                       state->i2c_edid_addr >> 1);
        if (state->i2c_edid == NULL) {
                v4l2_err(sd, "failed to register edid i2c client\n");
                err = -ENOMEM;
                goto err_entity;
        }
 
+       adv7511_wr(sd, 0xe1, state->i2c_cec_addr);
+       if (state->pdata.cec_clk < 3000000 ||
+           state->pdata.cec_clk > 100000000) {
+               v4l2_err(sd, "%s: cec_clk %u outside range, disabling cec\n",
+                               __func__, state->pdata.cec_clk);
+               state->pdata.cec_clk = 0;
+       }
+
+       if (state->pdata.cec_clk) {
+               state->i2c_cec = i2c_new_dummy(client->adapter,
+                                              state->i2c_cec_addr >> 1);
+               if (state->i2c_cec == NULL) {
+                       v4l2_err(sd, "failed to register cec i2c client\n");
+                       goto err_unreg_edid;
+               }
+               adv7511_wr(sd, 0xe2, 0x00); /* power up cec section */
+       } else {
+               adv7511_wr(sd, 0xe2, 0x01); /* power down cec section */
+       }
+
        state->i2c_pktmem = i2c_new_dummy(client->adapter, state->i2c_pktmem_addr >> 1);
        if (state->i2c_pktmem == NULL) {
                v4l2_err(sd, "failed to register pktmem i2c client\n");
                err = -ENOMEM;
-               goto err_unreg_edid;
+               goto err_unreg_cec;
        }
 
-       adv7511_wr(sd, 0xe2, 0x01); /* power down cec section */
        state->work_queue = create_singlethread_workqueue(sd->name);
        if (state->work_queue == NULL) {
                v4l2_err(sd, "could not create workqueue\n");
@@ -1546,6 +1906,19 @@ static int adv7511_probe(struct i2c_client *client, const struct i2c_device_id *
        INIT_DELAYED_WORK(&state->edid_handler, adv7511_edid_handler);
 
        adv7511_init_setup(sd);
+
+#if IS_ENABLED(CONFIG_VIDEO_ADV7511_CEC)
+       state->cec_adap = cec_allocate_adapter(&adv7511_cec_adap_ops,
+               state, dev_name(&client->dev), CEC_CAP_TRANSMIT |
+               CEC_CAP_LOG_ADDRS | CEC_CAP_PASSTHROUGH | CEC_CAP_RC,
+               ADV7511_MAX_ADDRS, &client->dev);
+       err = PTR_ERR_OR_ZERO(state->cec_adap);
+       if (err) {
+               destroy_workqueue(state->work_queue);
+               goto err_unreg_pktmem;
+       }
+#endif
+
        adv7511_set_isr(sd, true);
        adv7511_check_monitor_present_status(sd);
 
@@ -1555,6 +1928,9 @@ static int adv7511_probe(struct i2c_client *client, const struct i2c_device_id *
 
 err_unreg_pktmem:
        i2c_unregister_device(state->i2c_pktmem);
+err_unreg_cec:
+       if (state->i2c_cec)
+               i2c_unregister_device(state->i2c_cec);
 err_unreg_edid:
        i2c_unregister_device(state->i2c_edid);
 err_entity:
@@ -1576,9 +1952,12 @@ static int adv7511_remove(struct i2c_client *client)
        v4l2_dbg(1, debug, sd, "%s removed @ 0x%x (%s)\n", client->name,
                 client->addr << 1, client->adapter->name);
 
+       adv7511_set_isr(sd, false);
        adv7511_init_setup(sd);
        cancel_delayed_work(&state->edid_handler);
        i2c_unregister_device(state->i2c_edid);
+       if (state->i2c_cec)
+               i2c_unregister_device(state->i2c_cec);
        i2c_unregister_device(state->i2c_pktmem);
        destroy_workqueue(state->work_queue);
        v4l2_device_unregister_subdev(sd);