summaryrefslogtreecommitdiff
path: root/drivers/media/i2c/adv7604.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/media/i2c/adv7604.c')
-rw-r--r--drivers/media/i2c/adv7604.c118
1 files changed, 86 insertions, 32 deletions
diff --git a/drivers/media/i2c/adv7604.c b/drivers/media/i2c/adv7604.c
index 09004d928d11..3049aa2fd0f0 100644
--- a/drivers/media/i2c/adv7604.c
+++ b/drivers/media/i2c/adv7604.c
@@ -73,6 +73,8 @@ MODULE_LICENSE("GPL");
#define ADV76XX_MAX_ADDRS (3)
+#define ADV76XX_MAX_EDID_BLOCKS 4
+
enum adv76xx_type {
ADV7604,
ADV7611,
@@ -108,6 +110,11 @@ struct adv76xx_chip_info {
unsigned int edid_enable_reg;
unsigned int edid_status_reg;
+ unsigned int edid_segment_reg;
+ unsigned int edid_segment_mask;
+ unsigned int edid_spa_loc_reg;
+ unsigned int edid_spa_loc_msb_mask;
+ unsigned int edid_spa_port_b_reg;
unsigned int lcf_reg;
unsigned int cable_det_mask;
@@ -176,7 +183,7 @@ struct adv76xx_state {
const struct adv76xx_format_info *format;
struct {
- u8 edid[256];
+ u8 edid[ADV76XX_MAX_EDID_BLOCKS * 128];
u32 present;
unsigned blocks;
} edid;
@@ -512,10 +519,17 @@ static inline int edid_write_block(struct v4l2_subdev *sd,
static void adv76xx_set_hpd(struct adv76xx_state *state, unsigned int hpd)
{
+ const struct adv76xx_chip_info *info = state->info;
unsigned int i;
- for (i = 0; i < state->info->num_dv_ports; ++i)
- gpiod_set_value_cansleep(state->hpd_gpio[i], hpd & BIT(i));
+ if (info->type == ADV7604) {
+ for (i = 0; i < state->info->num_dv_ports; ++i)
+ gpiod_set_value_cansleep(state->hpd_gpio[i], hpd & BIT(i));
+ } else {
+ for (i = 0; i < state->info->num_dv_ports; ++i)
+ io_write_clr_set(&state->sd, 0x20, 0x80 >> i,
+ (!!(hpd & BIT(i))) << (7 - i));
+ }
v4l2_subdev_notify(&state->sd, ADV76XX_HOTPLUG, &hpd);
}
@@ -2298,7 +2312,7 @@ static int adv76xx_set_edid(struct v4l2_subdev *sd, struct v4l2_edid *edid)
struct adv76xx_state *state = to_state(sd);
const struct adv76xx_chip_info *info = state->info;
unsigned int spa_loc;
- u16 pa;
+ u16 pa, parent_pa;
int err;
int i;
@@ -2327,15 +2341,25 @@ static int adv76xx_set_edid(struct v4l2_subdev *sd, struct v4l2_edid *edid)
__func__, edid->pad, state->edid.present);
return 0;
}
- if (edid->blocks > 2) {
- edid->blocks = 2;
+ if (edid->blocks > ADV76XX_MAX_EDID_BLOCKS) {
+ edid->blocks = ADV76XX_MAX_EDID_BLOCKS;
return -E2BIG;
}
+
pa = v4l2_get_edid_phys_addr(edid->edid, edid->blocks * 128, &spa_loc);
- err = v4l2_phys_addr_validate(pa, &pa, NULL);
+ err = v4l2_phys_addr_validate(pa, &parent_pa, NULL);
if (err)
return err;
+ if (!spa_loc) {
+ /*
+ * There is no SPA, so just set spa_loc to 128 and pa to whatever
+ * data is there.
+ */
+ spa_loc = 128;
+ pa = (edid->edid[spa_loc] << 8) | edid->edid[spa_loc + 1];
+ }
+
v4l2_dbg(2, debug, sd, "%s: write EDID pad %d, edid.present = 0x%x\n",
__func__, edid->pad, state->edid.present);
@@ -2344,41 +2368,33 @@ static int adv76xx_set_edid(struct v4l2_subdev *sd, struct v4l2_edid *edid)
adv76xx_set_hpd(state, 0);
rep_write_clr_set(sd, info->edid_enable_reg, 0x0f, 0x00);
- /*
- * Return an error if no location of the source physical address
- * was found.
- */
- if (spa_loc == 0)
- return -EINVAL;
-
switch (edid->pad) {
case ADV76XX_PAD_HDMI_PORT_A:
- state->spa_port_a[0] = edid->edid[spa_loc];
- state->spa_port_a[1] = edid->edid[spa_loc + 1];
+ state->spa_port_a[0] = pa >> 8;
+ state->spa_port_a[1] = pa & 0xff;
break;
case ADV7604_PAD_HDMI_PORT_B:
- rep_write(sd, 0x70, edid->edid[spa_loc]);
- rep_write(sd, 0x71, edid->edid[spa_loc + 1]);
+ rep_write(sd, info->edid_spa_port_b_reg, pa >> 8);
+ rep_write(sd, info->edid_spa_port_b_reg + 1, pa & 0xff);
break;
case ADV7604_PAD_HDMI_PORT_C:
- rep_write(sd, 0x72, edid->edid[spa_loc]);
- rep_write(sd, 0x73, edid->edid[spa_loc + 1]);
+ rep_write(sd, info->edid_spa_port_b_reg + 2, pa >> 8);
+ rep_write(sd, info->edid_spa_port_b_reg + 3, pa & 0xff);
break;
case ADV7604_PAD_HDMI_PORT_D:
- rep_write(sd, 0x74, edid->edid[spa_loc]);
- rep_write(sd, 0x75, edid->edid[spa_loc + 1]);
+ rep_write(sd, info->edid_spa_port_b_reg + 4, pa >> 8);
+ rep_write(sd, info->edid_spa_port_b_reg + 5, pa & 0xff);
break;
default:
return -EINVAL;
}
- if (info->type == ADV7604) {
- rep_write(sd, 0x76, spa_loc & 0xff);
- rep_write_clr_set(sd, 0x77, 0x40, (spa_loc & 0x100) >> 2);
- } else {
- /* ADV7612 Software Manual Rev. A, p. 15 */
- rep_write(sd, 0x70, spa_loc & 0xff);
- rep_write_clr_set(sd, 0x71, 0x01, (spa_loc & 0x100) >> 8);
+ if (info->edid_spa_loc_reg) {
+ u8 mask = info->edid_spa_loc_msb_mask;
+
+ rep_write(sd, info->edid_spa_loc_reg, spa_loc & 0xff);
+ rep_write_clr_set(sd, info->edid_spa_loc_reg + 1,
+ mask, (spa_loc & 0x100) ? mask : 0);
}
edid->edid[spa_loc] = state->spa_port_a[0];
@@ -2390,11 +2406,25 @@ static int adv76xx_set_edid(struct v4l2_subdev *sd, struct v4l2_edid *edid)
edid->edid[0x16]);
state->edid.present |= 1 << edid->pad;
- err = edid_write_block(sd, 128 * edid->blocks, state->edid.edid);
+ rep_write_clr_set(sd, info->edid_segment_reg,
+ info->edid_segment_mask, 0);
+ err = edid_write_block(sd, 128 * min(edid->blocks, 2U), state->edid.edid);
if (err < 0) {
v4l2_err(sd, "error %d writing edid pad %d\n", err, edid->pad);
return err;
}
+ if (edid->blocks > 2) {
+ rep_write_clr_set(sd, info->edid_segment_reg,
+ info->edid_segment_mask,
+ info->edid_segment_mask);
+ err = edid_write_block(sd, 128 * (edid->blocks - 2),
+ state->edid.edid + 256);
+ if (err < 0) {
+ v4l2_err(sd, "error %d writing edid pad %d\n",
+ err, edid->pad);
+ return err;
+ }
+ }
/* adv76xx calculates the checksums and enables I2C access to internal
EDID RAM from DDC port. */
@@ -2409,7 +2439,7 @@ static int adv76xx_set_edid(struct v4l2_subdev *sd, struct v4l2_edid *edid)
v4l2_err(sd, "error enabling edid (0x%x)\n", state->edid.present);
return -EIO;
}
- cec_s_phys_addr(state->cec_adap, pa, false);
+ cec_s_phys_addr(state->cec_adap, parent_pa, false);
/* enable hotplug after 100 ms */
schedule_delayed_work(&state->delayed_work_enable_hotplug, HZ / 10);
@@ -2803,6 +2833,18 @@ static int adv76xx_core_init(struct v4l2_subdev *sd)
io_write(sd, 0x0b, 0x44); /* Power down ESDP block */
cp_write(sd, 0xcf, 0x01); /* Power down macrovision */
+ /* HPD */
+ if (info->type != ADV7604) {
+ /* Set manual HPD values to 0 */
+ io_write_clr_set(sd, 0x20, 0xc0, 0);
+ /*
+ * Set HPA_DELAY to 200 ms and set automatic HPD control
+ * to: internal EDID is active AND a cable is detected
+ * AND the manual HPD control is set to 1.
+ */
+ hdmi_write_clr_set(sd, 0x6c, 0xf6, 0x26);
+ }
+
/* video format */
io_write_clr_set(sd, 0x02, 0x0f, pdata->alt_gamma << 3);
io_write_clr_set(sd, 0x05, 0x0e, pdata->blank_data << 3 |
@@ -2987,6 +3029,11 @@ static const struct adv76xx_chip_info adv76xx_chip_info[] = {
.num_dv_ports = 4,
.edid_enable_reg = 0x77,
.edid_status_reg = 0x7d,
+ .edid_segment_reg = 0x77,
+ .edid_segment_mask = 0x10,
+ .edid_spa_loc_reg = 0x76,
+ .edid_spa_loc_msb_mask = 0x40,
+ .edid_spa_port_b_reg = 0x70,
.lcf_reg = 0xb3,
.tdms_lock_mask = 0xe0,
.cable_det_mask = 0x1e,
@@ -3037,6 +3084,8 @@ static const struct adv76xx_chip_info adv76xx_chip_info[] = {
.num_dv_ports = 1,
.edid_enable_reg = 0x74,
.edid_status_reg = 0x76,
+ .edid_segment_reg = 0x7a,
+ .edid_segment_mask = 0x01,
.lcf_reg = 0xa3,
.tdms_lock_mask = 0x43,
.cable_det_mask = 0x01,
@@ -3081,6 +3130,11 @@ static const struct adv76xx_chip_info adv76xx_chip_info[] = {
.num_dv_ports = 1, /* normally 2 */
.edid_enable_reg = 0x74,
.edid_status_reg = 0x76,
+ .edid_segment_reg = 0x7a,
+ .edid_segment_mask = 0x01,
+ .edid_spa_loc_reg = 0x70,
+ .edid_spa_loc_msb_mask = 0x01,
+ .edid_spa_port_b_reg = 0x52,
.lcf_reg = 0xa3,
.tdms_lock_mask = 0x43,
.cable_det_mask = 0x01,
@@ -3616,7 +3670,7 @@ static int adv76xx_remove(struct i2c_client *client)
io_write(sd, 0x6e, 0);
io_write(sd, 0x73, 0);
- cancel_delayed_work(&state->delayed_work_enable_hotplug);
+ cancel_delayed_work_sync(&state->delayed_work_enable_hotplug);
v4l2_async_unregister_subdev(sd);
media_entity_cleanup(&sd->entity);
adv76xx_unregister_clients(to_state(sd));