Hi Niklas On 13/06/17 08:33, Niklas Söderlund wrote: > Hi Kieran, > > Thanks for your patch, and great work! Thanks for taking a look. > On 2017-06-13 01:35:07 +0100, Kieran Bingham wrote: >> From: Kieran Bingham <kieran.bingham+renesas@xxxxxxxxxxxxxxxx> >> >> Provide support for the ADV7481 and ADV7482. >> >> The driver is modelled with 4 subdevices to allow simultaneous streaming >> from the AFE (Analog front end) and HDMI inputs though two CSI TX >> entities. >> >> The HDMI entity is linked to the TXA CSI bus, whilst the AFE is linked >> to the TXB CSI bus. >> >> The driver is based on a prototype by Koji Matsuoka in the Renesas BSP, >> and an earlier rework by Niklas Söderlund. >> >> Signed-off-by: Kieran Bingham <kieran.bingham+renesas@xxxxxxxxxxxxxxxx> >> <snip> >> +static int adv748x_afe_get_format(struct v4l2_subdev *sd, >> + struct v4l2_subdev_pad_config *cfg, >> + struct v4l2_subdev_format *sdformat) >> +{ >> + struct adv748x_afe *afe = adv748x_sd_to_afe(sd); >> + struct v4l2_mbus_framefmt *mbusformat; >> + >> + /* The format of the analog sink pads is nonsensical */ >> + if (sdformat->pad != ADV748X_AFE_SOURCE) >> + return -EINVAL; >> + >> + if (sdformat->which == V4L2_SUBDEV_FORMAT_TRY) { >> + mbusformat = v4l2_subdev_get_try_format(sd, cfg, sdformat->pad); >> + sdformat->format = *mbusformat; >> + } else { >> + adv748x_afe_fill_format(afe, &sdformat->format); >> + adv748x_afe_set_pixelrate(afe); >> + } >> + >> + return 0; >> +} >> + >> +static int adv748x_afe_set_format(struct v4l2_subdev *sd, >> + struct v4l2_subdev_pad_config *cfg, >> + struct v4l2_subdev_format *sdformat) >> +{ >> + struct v4l2_mbus_framefmt *mbusformat; >> + >> + /* The format of the analog sink pads is nonsensical */ >> + if (sdformat->pad != ADV748X_AFE_SOURCE) >> + return -EINVAL; >> + >> + if (sdformat->which == V4L2_SUBDEV_FORMAT_ACTIVE) >> + return adv748x_afe_get_format(sd, cfg, sdformat); >> + >> + mbusformat = v4l2_subdev_get_try_format(sd, cfg, sdformat->pad); >> + *mbusformat = sdformat->format; > > Hum, for the V4L2_SUBDEV_FORMAT_TRY case will this not accept any format > provided to the function? Should you not limit this to the device > capabilities before assigning it to mbusformat ? Hrmmm maybe it got too late last night :) I was trying to remove the effect of the active setting on the TRY format, and I've gone too far :) > >> + >> + return 0; >> +} <snip> >> + >> +static int adv748x_setup_links(struct adv748x_state *state) >> +{ >> + int ret; >> + int enabled = MEDIA_LNK_FL_ENABLED; >> + >> +/* >> + * HACK/Workaround: >> + * >> + * Currently non-immutable link resets go through the RVin >> + * driver, and cause the links to fail, due to not being part of RVIN. >> + * As a temporary workaround until the RVIN driver knows better than to parse >> + * links that do not belong to it, use static immutable links for our internal >> + * media paths. >> + */ > > The problem is a bigger then just the VIN ignoring the links not > belonging to it self I think. The ADV7482 driver must have a link > notification handler to deal with the links that belong to it. > > Else if all links of the media device is reset there is no way to setup > the links between the different ADV7482 subdevices, or am I missing > something? Ahhh -- this function shouldn't even be in here ! It's not meant to be used - Links are now created in adv748x_csi2_register_link() so now I'm concerned why I didn't get an unused function compiler warning :) However - your point still stands. > >> +#define ADV748x_DEV_STATIC_LINKS >> +#ifdef ADV748x_DEV_STATIC_LINKS >> + enabled |= MEDIA_LNK_FL_IMMUTABLE; >> +#endif >> + >> + /* TXA - Default link is with HDMI */ >> + ret = media_create_pad_link(&state->hdmi.sd.entity, 1, >> + &state->txa.sd.entity, 0, enabled); >> + if (ret) { >> + adv_err(state, "Failed to create HDMI-TXA pad link"); >> + return ret; >> + } >> + >> +#ifndef ADV748x_DEV_STATIC_LINKS >> + ret = media_create_pad_link(&state->afe.sd.entity, ADV748X_AFE_SOURCE, >> + &state->txa.sd.entity, 0, 0); >> + if (ret) { >> + adv_err(state, "Failed to create AFE-TXA pad link"); >> + return ret; >> + } >> +#endif >> + >> + /* TXB - Can only output from the AFE */ >> + ret = media_create_pad_link(&state->afe.sd.entity, ADV748X_AFE_SOURCE, >> + &state->txb.sd.entity, 0, enabled); >> + if (ret) { >> + adv_err(state, "Failed to create AFE-TXB pad link"); >> + return ret; >> + } >> + >> + return 0; >> +} >> + >> +int adv748x_register_subdevs(struct adv748x_state *state, >> + struct v4l2_device *v4l2_dev) And that's why I didn't get a function unused warning from the compiler. adv748x_setup_links() is called by adv748x_register_subdevs() which is not static, but is never used. I've just removed these two functions. - the new linking mechanism is handled during the registration of the CSI2 entitiy. However your consideration about links resetting is still valid - as I have only been testing with immutable links which will have prevented me from seeing the issues. >> +{ >> + int ret; >> + >> + ret = v4l2_device_register_subdev(v4l2_dev, &state->hdmi.sd); >> + if (ret < 0) >> + return ret; >> + >> + ret = v4l2_device_register_subdev(v4l2_dev, &state->afe.sd); >> + if (ret < 0) >> + goto err_unregister_hdmi; >> + >> + ret = adv748x_setup_links(state); >> + if (ret < 0) >> + goto err_unregister_afe; >> + >> + return 0; >> + >> +err_unregister_afe: >> + v4l2_device_unregister_subdev(&state->afe.sd); >> +err_unregister_hdmi: >> + v4l2_device_unregister_subdev(&state->hdmi.sd); >> + >> + return ret; >> +} >> + <snip> >> +static int adv748x_csi2_get_format(struct v4l2_subdev *sd, >> + struct v4l2_subdev_pad_config *cfg, >> + struct v4l2_subdev_format *sdformat) >> +{ >> + struct adv748x_csi2 *tx = adv748x_sd_to_csi2(sd); >> + struct adv748x_state *state = tx->state; >> + struct v4l2_mbus_framefmt *mbusformat; >> + >> + mbusformat = adv748x_csi2_get_pad_format(sd, cfg, sdformat->pad, >> + sdformat->which); >> + if (!mbusformat) >> + return -EINVAL; >> + >> + mutex_lock(&state->mutex); > > Why do you need to take the lock here? I'm not saying it's wrong just > curious :-) > I think the get/set formats are both userspace API's that 'could' be accessed in parallel which read/modify the same context ... >> + >> + sdformat->format = *mbusformat; >> + >> + mutex_unlock(&state->mutex); >> + >> + return 0; >> +} >> + >> +static int adv748x_csi2_set_format(struct v4l2_subdev *sd, >> + struct v4l2_subdev_pad_config *cfg, >> + struct v4l2_subdev_format *sdformat) >> +{ >> + struct adv748x_csi2 *tx = adv748x_sd_to_csi2(sd); >> + struct adv748x_state *state = tx->state; >> + struct v4l2_mbus_framefmt *mbusformat; >> + int ret = 0; >> + >> + mbusformat = adv748x_csi2_get_pad_format(sd, cfg, sdformat->pad, >> + sdformat->which); >> + if (!mbusformat) >> + return -EINVAL; >> + >> + mutex_lock(&state->mutex); > > Also why you need to lock here? As above... if this is extraneous I'll remove it. >> + >> +static int adv748x_hdmi_read_pixelclock(struct adv748x_state *state) >> +{ >> + int a, b; >> + >> + a = hdmi_read(state, ADV748X_HDMI_TMDS_1); >> + b = hdmi_read(state, ADV748X_HDMI_TMDS_2); >> + if (a < 0 || b < 0) >> + return -ENODATA; >> + >> + /* >> + * The High 9 bits store TMDS frequency measurement in MHz > > s/High/high/ > fixed. >> + * The low 7 bits of TMDS_2 store the 7-bit TMDS fractional frequency >> + * measurement in 1/128 MHz >> + */ >> + return ((a << 1) | (b >> 7)) * 1000000 + (b & 0x7f) * 1000000 / 128; >> +} >> + >> +/* <snip> >> + >> +static int adv748x_hdmi_g_dv_timings(struct v4l2_subdev *sd, >> + struct v4l2_dv_timings *timings) >> +{ >> + struct adv748x_hdmi *hdmi = adv748x_sd_to_hdmi(sd); >> + struct adv748x_state *state = adv748x_hdmi_to_state(hdmi); >> + >> + mutex_lock(&state->mutex); > > Why you need to take the lock here? User accessible get/setters... But it doesn't look like other drivers do the same here. Maybe I went overkill adding extra locking earlier. > >> + >> + *timings = hdmi->timings; >> + >> + mutex_unlock(&state->mutex); >> + >> + return 0; >> +} >> + >> +static int adv748x_hdmi_query_dv_timings(struct v4l2_subdev *sd, >> + struct v4l2_dv_timings *timings) >> +{ >> + struct adv748x_hdmi *hdmi = adv748x_sd_to_hdmi(sd); >> + struct adv748x_state *state = adv748x_hdmi_to_state(hdmi); >> + struct v4l2_bt_timings *bt = &timings->bt; >> + int pixelclock; >> + int polarity; >> + >> + if (!timings) >> + return -EINVAL; >> + >> + memset(timings, 0, sizeof(struct v4l2_dv_timings)); >> + >> + if (!adv748x_hdmi_has_signal(state)) >> + return -ENOLINK; >> + >> + pixelclock = adv748x_hdmi_read_pixelclock(state); >> + if (pixelclock < 0) >> + return -ENODATA; >> + >> + timings->type = V4L2_DV_BT_656_1120; >> + >> + bt->pixelclock = pixelclock; >> + bt->interlaced = hdmi_read(state, ADV748X_HDMI_F1H1) & >> + ADV748X_HDMI_F1H1_INTERLACED ? >> + V4L2_DV_INTERLACED : V4L2_DV_PROGRESSIVE; >> + bt->width = hdmi_read16(state, ADV748X_HDMI_LW1, >> + ADV748X_HDMI_LW1_WIDTH_MASK); >> + bt->height = hdmi_read16(state, ADV748X_HDMI_F0H1, >> + ADV748X_HDMI_F0H1_HEIGHT_MASK); >> + bt->hfrontporch = hdmi_read16(state, ADV748X_HDMI_HFRONT_PORCH, >> + ADV748X_HDMI_HFRONT_PORCH_MASK); >> + bt->hsync = hdmi_read16(state, ADV748X_HDMI_HSYNC_WIDTH, >> + ADV748X_HDMI_HSYNC_WIDTH_MASK); >> + bt->hbackporch = hdmi_read16(state, ADV748X_HDMI_HBACK_PORCH, >> + ADV748X_HDMI_HBACK_PORCH_MASK); >> + bt->vfrontporch = hdmi_read16(state, ADV748X_HDMI_VFRONT_PORCH, >> + ADV748X_HDMI_VFRONT_PORCH_MASK) / 2; >> + bt->vsync = hdmi_read16(state, ADV748X_HDMI_VSYNC_WIDTH, >> + ADV748X_HDMI_VSYNC_WIDTH_MASK) / 2; >> + bt->vbackporch = hdmi_read16(state, ADV748X_HDMI_VBACK_PORCH, >> + ADV748X_HDMI_VBACK_PORCH_MASK) / 2; >> + > > Extra newline. Ah yes, I just found a nice sed to catch and remove these automatically. sed 'N;/^\n$/D;P;D;' Picked up 2 more lines from -core.c :) That should be part of checkpatch.pl somewhere ... Then I'd catch them as I commit. > >> + >> + polarity = hdmi_read(state, 0x05); >> + bt->polarities = (polarity & BIT(4) ? V4L2_DV_VSYNC_POS_POL : 0) | >> + (polarity & BIT(5) ? V4L2_DV_HSYNC_POS_POL : 0); >> + >> + if (bt->interlaced == V4L2_DV_INTERLACED) { >> + bt->height += hdmi_read16(state, 0x0b, 0x1fff); >> + bt->il_vfrontporch = hdmi_read16(state, 0x2c, 0x3fff) / 2; >> + bt->il_vsync = hdmi_read16(state, 0x30, 0x3fff) / 2; >> + bt->il_vbackporch = hdmi_read16(state, 0x34, 0x3fff) / 2; >> + } >> + >> + adv748x_fill_optional_dv_timings(timings); >> + >> + /* >> + * No interrupt handling is implemented yet. >> + * There should be an IRQ when a cable is plugged and the new timings >> + * should be figured out and stored to state. >> + */ >> + hdmi->timings = *timings; >> + >> + return 0; >> +} >> + >> +static int adv748x_hdmi_g_input_status(struct v4l2_subdev *sd, u32 *status) >> +{ >> + struct adv748x_hdmi *hdmi = adv748x_sd_to_hdmi(sd); >> + struct adv748x_state *state = adv748x_hdmi_to_state(hdmi); >> + >> + mutex_lock(&state->mutex); > > Lock ? :-) > Now this one is talking to the i2c bus ... so I want to keep that - although now I've converted to regmap - it might well be locking the bus for me ... I'll have to check. >> + >> + *status = adv748x_hdmi_has_signal(state) ? 0 : V4L2_IN_ST_NO_SIGNAL; >> + >> + mutex_unlock(&state->mutex); >> + >> + return 0; >> +} >> + <snip> >> + >> +static int adv748x_hdmi_set_format(struct v4l2_subdev *sd, >> + struct v4l2_subdev_pad_config *cfg, >> + struct v4l2_subdev_format *sdformat) >> +{ >> + struct v4l2_mbus_framefmt *mbusformat; >> + >> + if (sdformat->pad != ADV748X_HDMI_SOURCE) >> + return -EINVAL; >> + >> + if (sdformat->which == V4L2_SUBDEV_FORMAT_ACTIVE) >> + return adv748x_hdmi_get_format(sd, cfg, sdformat); >> + >> + mbusformat = v4l2_subdev_get_try_format(sd, cfg, sdformat->pad); >> + *mbusformat = sdformat->format; > > Same comment as for adv748x_afe_set_format(). Ack. > >> + >> + return 0; >> +} >> + Thanks :)