media: adv7842: support 1 block EDIDs, fix clearing EDID

Add support for EDIDs consisting of one EDID block.

Related to this, improve CEC physical address handling.

Clearing the EDID caused a bug since v4l2_calc_aspect_ratio() was
called with a NULL pointer.

Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl>
Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
This commit is contained in:
Hans Verkuil 2021-04-07 16:16:18 +02:00 committed by Mauro Carvalho Chehab
parent e0a4205d65
commit 3e057b8a5f

View file

@ -99,10 +99,12 @@ struct adv7842_state {
v4l2_std_id norm;
struct {
u8 edid[256];
u32 blocks;
u32 present;
} hdmi_edid;
struct {
u8 edid[256];
u32 blocks;
u32 present;
} vga_edid;
struct v4l2_fract aspect_ratio;
@ -711,7 +713,8 @@ static int edid_write_vga_segment(struct v4l2_subdev *sd)
{
struct i2c_client *client = v4l2_get_subdevdata(sd);
struct adv7842_state *state = to_state(sd);
const u8 *val = state->vga_edid.edid;
const u8 *edid = state->vga_edid.edid;
u32 blocks = state->vga_edid.blocks;
int err = 0;
int i;
@ -726,10 +729,10 @@ static int edid_write_vga_segment(struct v4l2_subdev *sd)
/* edid segment pointer '1' for VGA port */
rep_write_and_or(sd, 0x77, 0xef, 0x10);
for (i = 0; !err && i < 256; i += I2C_SMBUS_BLOCK_MAX)
for (i = 0; !err && i < blocks * 128; i += I2C_SMBUS_BLOCK_MAX)
err = i2c_smbus_write_i2c_block_data(state->i2c_edid, i,
I2C_SMBUS_BLOCK_MAX,
val + i);
edid + i);
if (err)
return err;
@ -759,8 +762,9 @@ static int edid_write_hdmi_segment(struct v4l2_subdev *sd, u8 port)
struct i2c_client *client = v4l2_get_subdevdata(sd);
struct adv7842_state *state = to_state(sd);
const u8 *edid = state->hdmi_edid.edid;
u32 blocks = state->hdmi_edid.blocks;
int spa_loc;
u16 pa;
u16 pa, parent_pa;
int err = 0;
int i;
@ -778,33 +782,35 @@ static int edid_write_hdmi_segment(struct v4l2_subdev *sd, u8 port)
return 0;
}
pa = v4l2_get_edid_phys_addr(edid, 256, &spa_loc);
err = v4l2_phys_addr_validate(pa, &pa, NULL);
pa = v4l2_get_edid_phys_addr(edid, blocks * 128, &spa_loc);
err = v4l2_phys_addr_validate(pa, &parent_pa, NULL);
if (err)
return err;
/*
* Return an error if no location of the source physical address
* was found.
*/
if (spa_loc == 0)
return -EINVAL;
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[spa_loc] << 8) | edid[spa_loc + 1];
}
/* edid segment pointer '0' for HDMI ports */
rep_write_and_or(sd, 0x77, 0xef, 0x00);
for (i = 0; !err && i < 256; i += I2C_SMBUS_BLOCK_MAX)
for (i = 0; !err && i < blocks * 128; i += I2C_SMBUS_BLOCK_MAX)
err = i2c_smbus_write_i2c_block_data(state->i2c_edid, i,
I2C_SMBUS_BLOCK_MAX, edid + i);
if (err)
return err;
if (port == ADV7842_EDID_PORT_A) {
rep_write(sd, 0x72, edid[spa_loc]);
rep_write(sd, 0x73, edid[spa_loc + 1]);
rep_write(sd, 0x72, pa >> 8);
rep_write(sd, 0x73, pa & 0xff);
} else {
rep_write(sd, 0x74, edid[spa_loc]);
rep_write(sd, 0x75, edid[spa_loc + 1]);
rep_write(sd, 0x74, pa >> 8);
rep_write(sd, 0x75, pa & 0xff);
}
rep_write(sd, 0x76, spa_loc & 0xff);
rep_write_and_or(sd, 0x77, 0xbf, (spa_loc >> 2) & 0x40);
@ -824,7 +830,7 @@ static int edid_write_hdmi_segment(struct v4l2_subdev *sd, u8 port)
(port == ADV7842_EDID_PORT_A) ? 'A' : 'B');
return -EIO;
}
cec_s_phys_addr(state->cec_adap, pa, false);
cec_s_phys_addr(state->cec_adap, parent_pa, false);
/* enable hotplug after 200 ms */
schedule_delayed_work(&state->delayed_work_enable_hotplug, HZ / 5);
@ -2443,6 +2449,7 @@ static int adv7842_isr(struct v4l2_subdev *sd, u32 status, bool *handled)
static int adv7842_get_edid(struct v4l2_subdev *sd, struct v4l2_edid *edid)
{
struct adv7842_state *state = to_state(sd);
u32 blocks = 0;
u8 *data = NULL;
memset(edid->reserved, 0, sizeof(edid->reserved));
@ -2450,30 +2457,34 @@ static int adv7842_get_edid(struct v4l2_subdev *sd, struct v4l2_edid *edid)
switch (edid->pad) {
case ADV7842_EDID_PORT_A:
case ADV7842_EDID_PORT_B:
if (state->hdmi_edid.present & (0x04 << edid->pad))
if (state->hdmi_edid.present & (0x04 << edid->pad)) {
data = state->hdmi_edid.edid;
blocks = state->hdmi_edid.blocks;
}
break;
case ADV7842_EDID_PORT_VGA:
if (state->vga_edid.present)
if (state->vga_edid.present) {
data = state->vga_edid.edid;
blocks = state->vga_edid.blocks;
}
break;
default:
return -EINVAL;
}
if (edid->start_block == 0 && edid->blocks == 0) {
edid->blocks = data ? 2 : 0;
edid->blocks = blocks;
return 0;
}
if (!data)
return -ENODATA;
if (edid->start_block >= 2)
if (edid->start_block >= blocks)
return -EINVAL;
if (edid->start_block + edid->blocks > 2)
edid->blocks = 2 - edid->start_block;
if (edid->start_block + edid->blocks > blocks)
edid->blocks = blocks - edid->start_block;
memcpy(edid->edid, data + edid->start_block * 128, edid->blocks * 128);
@ -2497,26 +2508,30 @@ static int adv7842_set_edid(struct v4l2_subdev *sd, struct v4l2_edid *e)
}
/* todo, per edid */
state->aspect_ratio = v4l2_calc_aspect_ratio(e->edid[0x15],
e->edid[0x16]);
if (e->blocks)
state->aspect_ratio = v4l2_calc_aspect_ratio(e->edid[0x15],
e->edid[0x16]);
switch (e->pad) {
case ADV7842_EDID_PORT_VGA:
memset(&state->vga_edid.edid, 0, 256);
state->vga_edid.blocks = e->blocks;
state->vga_edid.present = e->blocks ? 0x1 : 0x0;
memcpy(&state->vga_edid.edid, e->edid, 128 * e->blocks);
if (e->blocks)
memcpy(&state->vga_edid.edid, e->edid, 128 * e->blocks);
err = edid_write_vga_segment(sd);
break;
case ADV7842_EDID_PORT_A:
case ADV7842_EDID_PORT_B:
memset(&state->hdmi_edid.edid, 0, 256);
state->hdmi_edid.blocks = e->blocks;
if (e->blocks) {
state->hdmi_edid.present |= 0x04 << e->pad;
memcpy(&state->hdmi_edid.edid, e->edid, 128 * e->blocks);
} else {
state->hdmi_edid.present &= ~(0x04 << e->pad);
adv7842_s_detect_tx_5v_ctrl(sd);
}
memcpy(&state->hdmi_edid.edid, e->edid, 128 * e->blocks);
err = edid_write_hdmi_segment(sd, e->pad);
break;
default: