Hi Kieran, On 2017-07-14 17:45:02 +0100, Kieran Bingham wrote: > Hi Niklas > > On 13/07/17 07:28, Niklas Söderlund wrote: > > > > Hi Kieran, > > > > Thanks for your hard work. > > And thank you for your support! > > > On 2017-07-06 12:01:16 +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> > > > > Unfortunately I have hit a regression with v7. I use the latest R-Car > > Gen3 VIN and CSI-2 patches in conjunction with v6 and v7 of this series. > > And wile running v4l2-compliance on a VIN which is connected to the CVBS > > input it fails, see output at the end. > > Interesting, at first glance I cannot imagine what's breaking here... > > > If i substitute the adv748x driver patch with v6 version v4l2-compliance > > works as expected. It also works as expected for the HDMI inputs using > > both v6 and v7 of this series. I have tried using both a PAL and NTSC > > source so I don't think that the removal of auto detection by the > > adv748x driver itself in v7 is to blame, but I have not verified this. > > Is this to do with the change to V4L2_FIELD_ALTERNATE perhaps? Yes ;-) > > The subdevice will now have a different size which could affect buffer sizes at > the MMAP? (I'm inferring that the "test MMAP: FAIL" is the issue here) > > Unfortunately - if this is the case - I would push the 'blame' (required change) > back up to the handling in the RcarVIN driver ... :S > > I'm not trying to defer this back to you - but just thinking out loud :) Well blame should be placed where it belong, and in this case that is with me :-) > > It could be tested quite quickly by changing in adv748x_afe_fill_format() > > - fmt->field = V4L2_FIELD_ALTERNATE; > + fmt->field = V4L2_FIELD_INTERLACED; // (or such) > > (and remember to comment out the fmt->height /= 2; for halving of the size) Whit this change the regression is gone so the proper fix should be implemented in the VIN driver. Thanks for pointing this out! > > > I'll see if I can reproduce this on Monday. > > -- > Regards > > Kieran > > > > To ease replication let me point out the compliance.sh script in > > vin-tests which will replicate the MC setup before running the > > v4l2-compliance. > > > > # v4l2-compliance -s -d /dev/video26 > > v4l2-compliance SHA : [ 359.935042] rcar-vin e6ef1000.video: ================= START STATUS ================= > > d16a17abd1d8d788[ 359.944098] rcar-vin e6ef1000.video: ================== END STATUS ================== > > 5ca2f44fb295035278baa89c > > > > Driver Info: > > Driver name : rcar_vin > > Card type : R_Car_VIN > > Bus info : platform:e6ef1000.video > > Driver version: 4.12.0 > > Capabilities : 0x85200001 > > Video Capture > > Read/Write > > Streaming > > Extended Pix Format > > Device Capabilities > > Device Caps : 0x05200001 > > Video Capture > > Read/Write > > Streaming > > Extended Pix Format > > > > Compliance test for device /dev/video26 (not using libv4l2): > > > > Required ioctls: > > test VIDIOC_QUERYCAP: OK > > > > Allow for multiple opens: > > test second video open: OK > > test VIDIOC_QUERYCAP: OK > > test VIDIOC_G/S_PRIORITY: OK > > test for unlimited opens: OK > > > > Debug ioctls: > > test VIDIOC_DBG_G/S_REGISTER: OK (Not Supported) > > test VIDIOC_LOG_STATUS: OK > > > > Input ioctls: > > test VIDIOC_G/S_TUNER/ENUM_FREQ_BANDS: OK (Not Supported) > > test VIDIOC_G/S_FREQUENCY: OK (Not Supported) > > test VIDIOC_S_HW_FREQ_SEEK: OK (Not Supported) > > test VIDIOC_ENUMAUDIO: OK (Not Supported) > > test VIDIOC_G/S/ENUMINPUT: OK > > test VIDIOC_G/S_AUDIO: OK (Not Supported) > > Inputs: 1 Audio Inputs: 0 Tuners: 0 > > > > Output ioctls: > > test VIDIOC_G/S_MODULATOR: OK (Not Supported) > > test VIDIOC_G/S_FREQUENCY: OK (Not Supported) > > test VIDIOC_ENUMAUDOUT: OK (Not Supported) > > test VIDIOC_G/S/ENUMOUTPUT: OK (Not Supported) > > test VIDIOC_G/S_AUDOUT: OK (Not Supported) > > Outputs: 0 Audio Outputs: 0 Modulators: 0 > > > > Input/Output configuration ioctls: > > test VIDIOC_ENUM/G/S/QUERY_STD: OK (Not Supported) > > test VIDIOC_ENUM/G/S/QUERY_DV_TIMINGS: OK (Not Supported) > > test VIDIOC_DV_TIMINGS_CAP: OK (Not Supported) > > test VIDIOC_G/S_EDID: OK (Not Supported) > > > > Test input 0: > > > > Control ioctls: > > test VIDIOC_QUERY_EXT_CTRL/QUERYMENU: OK (Not Supported) > > test VIDIOC_QUERYCTRL: OK (Not Supported) > > test VIDIOC_G/S_CTRL: OK (Not Supported) > > test VIDIOC_G/S/TRY_EXT_CTRLS: OK (Not Supported) > > test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: OK (Not Supported) > > test VIDIOC_G/S_JPEGCOMP: OK (Not Supported) > > Standard Controls: 0 Private Controls: 0 > > > > Format ioctls: > > test VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS: OK > > test VIDIOC_G/S_PARM: OK (Not Supported) > > test VIDIOC_G_FBUF: OK (Not Supported) > > test VIDIOC_G_FMT: OK > > test VIDIOC_TRY_FMT: OK > > test VIDIOC_S_FMT: OK > > test VIDIOC_G_SLICED_VBI_CAP: OK (Not Supported) > > test Cropping: OK (Not Supported) > > test Composing: OK (Not Supported) > > test Scaling: OK > > > > Codec ioctls: > > test VIDIOC_(TRY_)ENCODER_CMD: OK (Not Supported) > > test VIDIOC_G_ENC_INDEX: OK (Not Supported) > > test VIDIOC_(TRY_)DECODER_CMD: OK (Not Supported) > > > > Buffer ioctls: > > test VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: OK > > test VIDIOC_EXPBUF: OK > > > > Test input 0: > > > > Streaming ioctls: > > test read/write: OK > > fail: v4l2-test-buffers.cpp(308): (int)g_sequence() == seq.last_seq + 1 > > fail: v4l2-test-buffers.cpp(707): buf.check(q, last_seq) > > fail: v4l2-test-buffers.cpp(977): captureBufs(node, q, m2m_q, frame_count, false) > > test MMAP: FAIL > > test USERPTR: OK (Not Supported) > > test DMABUF: Cannot test, specify --expbuf-device > > > > > > Total: 46, Succeeded: 45, Failed: 1, Warnings: 0 > > > >> > >> --- > >> > >> v2: > >> - Implement DT parsing > >> - adv748x: Add CSI2 entity > >> - adv748x: Rework pad allocations and fmts > >> - Give AFE 8 input pads and move pad defines > >> - Use the enums to ensure pads are referenced correctly. > >> - adv748x: Rename AFE/HDMI entities > >> Now they are 'just afe' and 'just hdmi' > >> - Reorder the entity enum and structures > >> - Added pad-format for the CSI2 entities > >> - CSI2 s_stream pass through > >> - CSI2 control pass through (with link following) > >> > >> v3: > >> - dt: Extend DT documentation to specify interrupt mappings > >> - simplify adv748x_parse_dt > >> - core: Add banner to header file describing ADV748x variants > >> - Use entity structure pointers rather than global state pointers where > >> possible > >> - afe: Reduce indent on afe_status > >> - hdmi: Add error checking to the bt->pixelclock values. > >> - Remove all unnecessary pad checks: handled by core > >> - Fix all probe cleanup paths > >> - adv748x_csi2_probe() now fails if it has no endpoint > >> - csi2: Fix small oneliners for is_txa and get_remote_sd() > >> - csi2: Fix checkpatch warnings > >> - csi2: Fix up s_stream pass-through > >> - csi2: Fix up Pixel Rate passthrough > >> - csi2: simplify adv748x_csi2_get_pad_format() > >> - Remove 'async notifiers' from AFE/HDMI > >> Using async notifiers was overkill, when we have access to the > >> subdevices internally and can register them directly. > >> - Use state lock in control handlers and clean up s_ctrls > >> - remove _interruptible mutex locks > >> > >> v4: > >> - all: Convert hex 0xXX to lowercase > >> - all: Use defines instead of hardcoded register values > >> - all: Use regmap > >> - afe, csi2, hdmi: _probe -> _init > >> - afe, csi2, hdmi: _remove -> _cleanup > >> - afe, hdmi: Convert pattern generator to a control > >> - afe, hdmi: get/set-fmt refactor > >> - afe, hdmi, csi2: Convert to internal calls for pixelrate > >> - afe: Allow the AFE to configure the Input Select from DT > >> - afe: Reduce indent on adv748x_afe_status switch > >> - afe: Remove ununsed macro definitions AIN0-7 > >> - afe: Remove extraneous control checks handled by core > >> - afe: Comment fixups > >> - afe: Rename error label > >> - afe: Correct control names on the SDP > >> - afe: Use AIN0-7 rather than AIN1-8 to match ports and MC pads > >> - core: adv748x_parse_dt references to nodes, and catch multiple > >> endpoints in a port. > >> - core: adv748x_dt_cleanup to simplify releasing DT nodes > >> - core: adv748x_print_info renamed to adv748x_identify_chip > >> - core: reorganise ordering of probe sequence > >> - core: No need for of_match_ptr > >> - core: Fix up i2c read/writes (regmap still on todo list) > >> - core: Set specific functions from the entities on subdev-init > >> - core: Use kzalloc for state instead of devm > >> - core: Improve probe error reporting > >> - core: Track unknown BIT(6) in tx{a,b}_power > >> - csi2: Improve adv748x_csi2_get_remote_sd as adv748x_csi2_get_source_sd > >> - csi2: -EPIPE instead of -ENODEV > >> - csi2: adv_dbg, instead of adv_info > >> - csi2: adv748x_csi2_set_format fix > >> - csi2: remove async notifier and sd member variables > >> - csi2: register links from the CSI2 > >> - csi2: create virtual channel helper function > >> - dt: Remove numbering from endpoints > >> - dt: describe ports and interrupts as optional > >> - dt: Re-tab > >> - hdmi: adv748x_hdmi_have_signal -> adv748x_hdmi_has_signal > >> - hdmi: fix adv748x_hdmi_read_pixelclock return checks > >> - hdmi: improve adv748x_hdmi_set_video_timings > >> - hdmi: Fix tmp variable as polarity > >> - hdmi: Improve adv748x_hdmi_s_stream > >> - hdmi: Clean up adv748x_hdmi_s_ctrl register definitions and usage > >> - hdmi: Fix up adv748x_hdmi_s_dv_timings with macro names for register > >> - hdmi: Add locking to adv748x_hdmi_g_dv_timings > >> writes and locking > >> - hdmi: adv748x_hdmi_set_de_timings function added to clarify DE writes > >> - hdmi: Use CP in control register naming to match component processor > >> - hdmi: clean up adv748x_hdmi_query_dv_timings() > >> - KConfig: Fix up dependency and capitalisation > >> > >> v5: > >> - afe,hdmi: _set_pixelrate -> _propagate_pixelrate > >> - hdmi: Fix arm32 compilation failure : Use DIV_ROUND_CLOSEST_ULL > >> - core: remove unused link functions > >> - csi2: Use immutable links for HDMI->TXA, AFE->TXB > >> > >> v6: > >> - hdmi: Provide EDID support > >> - afe: Fix InLock inversion bug > >> - afe: Prevent autodetection of input format except in querystd > >> - afe,hdmi: Improve pattern generator control strings > >> - hdmi: Remove interlaced support capability (it's untested) > >> > >> v7: > >> - afe: Initialise std to V4L2_STD_NTSC_M and restore after query > >> - afe: use V4L2_FIELD_ALTERNATE > >> - hdmi: Define a minimum pixelclock value > >> - hdmi: Remove interlaced formats as they are untested > >> - Kconfig: Select REGMAP_I2C > >> - core: include linux/slab.h for kzalloc/kfree > >> > >> drivers/media/i2c/Kconfig | 12 +- > >> drivers/media/i2c/Makefile | 1 +- > >> drivers/media/i2c/adv748x/Makefile | 7 +- > >> drivers/media/i2c/adv748x/adv748x-afe.c | 552 ++++++++++++++++- > >> drivers/media/i2c/adv748x/adv748x-core.c | 832 ++++++++++++++++++++++++- > >> drivers/media/i2c/adv748x/adv748x-csi2.c | 327 +++++++++- > >> drivers/media/i2c/adv748x/adv748x-hdmi.c | 768 ++++++++++++++++++++++- > >> drivers/media/i2c/adv748x/adv748x.h | 425 ++++++++++++- > >> 8 files changed, 2924 insertions(+) > >> create mode 100644 drivers/media/i2c/adv748x/Makefile > >> create mode 100644 drivers/media/i2c/adv748x/adv748x-afe.c > >> create mode 100644 drivers/media/i2c/adv748x/adv748x-core.c > >> create mode 100644 drivers/media/i2c/adv748x/adv748x-csi2.c > >> create mode 100644 drivers/media/i2c/adv748x/adv748x-hdmi.c > >> create mode 100644 drivers/media/i2c/adv748x/adv748x.h > >> > >> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig > >> index 121b3b5394cb..fa2e7d8d4e3b 100644 > >> --- a/drivers/media/i2c/Kconfig > >> +++ b/drivers/media/i2c/Kconfig > >> @@ -204,6 +204,18 @@ config VIDEO_ADV7183 > >> To compile this driver as a module, choose M here: the > >> module will be called adv7183. > >> > >> +config VIDEO_ADV748X > >> + tristate "Analog Devices ADV748x decoder" > >> + depends on VIDEO_V4L2 && I2C && VIDEO_V4L2_SUBDEV_API > >> + depends on OF > >> + select REGMAP_I2C > >> + ---help--- > >> + V4L2 subdevice driver for the Analog Devices > >> + ADV7481 and ADV7482 HDMI/Analog video decoders. > >> + > >> + To compile this driver as a module, choose M here: the > >> + module will be called adv748x. > >> + > >> config VIDEO_ADV7604 > >> tristate "Analog Devices ADV7604 decoder" > >> depends on VIDEO_V4L2 && I2C && VIDEO_V4L2_SUBDEV_API > >> diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile > >> index 2c0868fa6034..30e856c02422 100644 > >> --- a/drivers/media/i2c/Makefile > >> +++ b/drivers/media/i2c/Makefile > >> @@ -28,6 +28,7 @@ obj-$(CONFIG_VIDEO_ADV7180) += adv7180.o > >> obj-$(CONFIG_VIDEO_ADV7183) += adv7183.o > >> obj-$(CONFIG_VIDEO_ADV7343) += adv7343.o > >> obj-$(CONFIG_VIDEO_ADV7393) += adv7393.o > >> +obj-$(CONFIG_VIDEO_ADV748X) += adv748x/ > >> obj-$(CONFIG_VIDEO_ADV7604) += adv7604.o > >> obj-$(CONFIG_VIDEO_ADV7842) += adv7842.o > >> obj-$(CONFIG_VIDEO_AD9389B) += ad9389b.o > >> diff --git a/drivers/media/i2c/adv748x/Makefile b/drivers/media/i2c/adv748x/Makefile > >> new file mode 100644 > >> index 000000000000..c0711e076f1d > >> --- /dev/null > >> +++ b/drivers/media/i2c/adv748x/Makefile > >> @@ -0,0 +1,7 @@ > >> +adv748x-objs := \ > >> + adv748x-afe.o \ > >> + adv748x-core.o \ > >> + adv748x-csi2.o \ > >> + adv748x-hdmi.o > >> + > >> +obj-$(CONFIG_VIDEO_ADV748X) += adv748x.o > >> diff --git a/drivers/media/i2c/adv748x/adv748x-afe.c b/drivers/media/i2c/adv748x/adv748x-afe.c > >> new file mode 100644 > >> index 000000000000..b33ccfc08708 > >> --- /dev/null > >> +++ b/drivers/media/i2c/adv748x/adv748x-afe.c > >> @@ -0,0 +1,552 @@ > >> +/* > >> + * Driver for Analog Devices ADV748X 8 channel analog front end (AFE) receiver > >> + * with standard definition processor (SDP) > >> + * > >> + * Copyright (C) 2017 Renesas Electronics Corp. > >> + * > >> + * This program is free software; you can redistribute it and/or modify it > >> + * under the terms of the GNU General Public License as published by the > >> + * Free Software Foundation; either version 2 of the License, or (at your > >> + * option) any later version. > >> + */ > >> + > >> +#include <linux/delay.h> > >> +#include <linux/module.h> > >> +#include <linux/mutex.h> > >> +#include <linux/v4l2-dv-timings.h> > >> + > >> +#include <media/v4l2-ctrls.h> > >> +#include <media/v4l2-device.h> > >> +#include <media/v4l2-dv-timings.h> > >> +#include <media/v4l2-ioctl.h> > >> + > >> +#include "adv748x.h" > >> + > >> +/* ----------------------------------------------------------------------------- > >> + * SDP > >> + */ > >> + > >> +#define ADV748X_AFE_STD_AD_PAL_BG_NTSC_J_SECAM 0x0 > >> +#define ADV748X_AFE_STD_AD_PAL_BG_NTSC_J_SECAM_PED 0x1 > >> +#define ADV748X_AFE_STD_AD_PAL_N_NTSC_J_SECAM 0x2 > >> +#define ADV748X_AFE_STD_AD_PAL_N_NTSC_M_SECAM 0x3 > >> +#define ADV748X_AFE_STD_NTSC_J 0x4 > >> +#define ADV748X_AFE_STD_NTSC_M 0x5 > >> +#define ADV748X_AFE_STD_PAL60 0x6 > >> +#define ADV748X_AFE_STD_NTSC_443 0x7 > >> +#define ADV748X_AFE_STD_PAL_BG 0x8 > >> +#define ADV748X_AFE_STD_PAL_N 0x9 > >> +#define ADV748X_AFE_STD_PAL_M 0xa > >> +#define ADV748X_AFE_STD_PAL_M_PED 0xb > >> +#define ADV748X_AFE_STD_PAL_COMB_N 0xc > >> +#define ADV748X_AFE_STD_PAL_COMB_N_PED 0xd > >> +#define ADV748X_AFE_STD_PAL_SECAM 0xe > >> +#define ADV748X_AFE_STD_PAL_SECAM_PED 0xf > >> + > >> +static int adv748x_afe_read_ro_map(struct adv748x_state *state, u8 reg) > >> +{ > >> + int ret; > >> + > >> + /* Select SDP Read-Only Main Map */ > >> + ret = sdp_write(state, ADV748X_SDP_MAP_SEL, > >> + ADV748X_SDP_MAP_SEL_RO_MAIN); > >> + if (ret < 0) > >> + return ret; > >> + > >> + return sdp_read(state, reg); > >> +} > >> + > >> +static int adv748x_afe_status(struct adv748x_afe *afe, u32 *signal, > >> + v4l2_std_id *std) > >> +{ > >> + struct adv748x_state *state = adv748x_afe_to_state(afe); > >> + int info; > >> + > >> + /* Read status from reg 0x10 of SDP RO Map */ > >> + info = adv748x_afe_read_ro_map(state, ADV748X_SDP_RO_10); > >> + if (info < 0) > >> + return info; > >> + > >> + if (signal) > >> + *signal = info & ADV748X_SDP_RO_10_IN_LOCK ? > >> + 0 : V4L2_IN_ST_NO_SIGNAL; > >> + > >> + if (!std) > >> + return 0; > >> + > >> + /* Standard not valid if there is no signal */ > >> + if (!(info & ADV748X_SDP_RO_10_IN_LOCK)) { > >> + *std = V4L2_STD_UNKNOWN; > >> + return 0; > >> + } > >> + > >> + switch (info & 0x70) { > >> + case 0x00: > >> + *std = V4L2_STD_NTSC; > >> + break; > >> + case 0x10: > >> + *std = V4L2_STD_NTSC_443; > >> + break; > >> + case 0x20: > >> + *std = V4L2_STD_PAL_M; > >> + break; > >> + case 0x30: > >> + *std = V4L2_STD_PAL_60; > >> + break; > >> + case 0x40: > >> + *std = V4L2_STD_PAL; > >> + break; > >> + case 0x50: > >> + *std = V4L2_STD_SECAM; > >> + break; > >> + case 0x60: > >> + *std = V4L2_STD_PAL_Nc | V4L2_STD_PAL_N; > >> + break; > >> + case 0x70: > >> + *std = V4L2_STD_SECAM; > >> + break; > >> + default: > >> + *std = V4L2_STD_UNKNOWN; > >> + break; > >> + } > >> + > >> + return 0; > >> +} > >> + > >> +static void adv748x_afe_fill_format(struct adv748x_afe *afe, > >> + struct v4l2_mbus_framefmt *fmt) > >> +{ > >> + memset(fmt, 0, sizeof(*fmt)); > >> + > >> + fmt->code = MEDIA_BUS_FMT_UYVY8_2X8; > >> + fmt->colorspace = V4L2_COLORSPACE_SMPTE170M; > >> + fmt->field = V4L2_FIELD_ALTERNATE; > >> + > >> + fmt->width = 720; > >> + fmt->height = afe->curr_norm & V4L2_STD_525_60 ? 480 : 576; > >> + > >> + /* Field height */ > >> + fmt->height /= 2; > >> +} > >> + > >> +static int adv748x_afe_std(v4l2_std_id std) > >> +{ > >> + if (std == V4L2_STD_PAL_60) > >> + return ADV748X_AFE_STD_PAL60; > >> + if (std == V4L2_STD_NTSC_443) > >> + return ADV748X_AFE_STD_NTSC_443; > >> + if (std == V4L2_STD_PAL_N) > >> + return ADV748X_AFE_STD_PAL_N; > >> + if (std == V4L2_STD_PAL_M) > >> + return ADV748X_AFE_STD_PAL_M; > >> + if (std == V4L2_STD_PAL_Nc) > >> + return ADV748X_AFE_STD_PAL_COMB_N; > >> + if (std & V4L2_STD_NTSC) > >> + return ADV748X_AFE_STD_NTSC_M; > >> + if (std & V4L2_STD_PAL) > >> + return ADV748X_AFE_STD_PAL_BG; > >> + if (std & V4L2_STD_SECAM) > >> + return ADV748X_AFE_STD_PAL_SECAM; > >> + > >> + return -EINVAL; > >> +} > >> + > >> +static void adv748x_afe_set_video_standard(struct adv748x_state *state, > >> + int sdpstd) > >> +{ > >> + sdp_clrset(state, ADV748X_SDP_VID_SEL, ADV748X_SDP_VID_SEL_MASK, > >> + (sdpstd & 0xf) << ADV748X_SDP_VID_SEL_SHIFT); > >> +} > >> + > >> +static int adv748x_afe_s_input(struct adv748x_afe *afe, unsigned int input) > >> +{ > >> + struct adv748x_state *state = adv748x_afe_to_state(afe); > >> + > >> + return sdp_write(state, ADV748X_SDP_INSEL, input); > >> +} > >> + > >> +static int adv748x_afe_g_pixelaspect(struct v4l2_subdev *sd, > >> + struct v4l2_fract *aspect) > >> +{ > >> + struct adv748x_afe *afe = adv748x_sd_to_afe(sd); > >> + > >> + if (afe->curr_norm & V4L2_STD_525_60) { > >> + aspect->numerator = 11; > >> + aspect->denominator = 10; > >> + } else { > >> + aspect->numerator = 54; > >> + aspect->denominator = 59; > >> + } > >> + > >> + return 0; > >> +} > >> + > >> +/* ----------------------------------------------------------------------------- > >> + * v4l2_subdev_video_ops > >> + */ > >> + > >> +static int adv748x_afe_g_std(struct v4l2_subdev *sd, v4l2_std_id *norm) > >> +{ > >> + struct adv748x_afe *afe = adv748x_sd_to_afe(sd); > >> + > >> + *norm = afe->curr_norm; > >> + > >> + return 0; > >> +} > >> + > >> +static int adv748x_afe_s_std(struct v4l2_subdev *sd, v4l2_std_id std) > >> +{ > >> + struct adv748x_afe *afe = adv748x_sd_to_afe(sd); > >> + struct adv748x_state *state = adv748x_afe_to_state(afe); > >> + int afe_std = adv748x_afe_std(std); > >> + > >> + if (afe_std < 0) > >> + return afe_std; > >> + > >> + mutex_lock(&state->mutex); > >> + > >> + adv748x_afe_set_video_standard(state, afe_std); > >> + afe->curr_norm = std; > >> + > >> + mutex_unlock(&state->mutex); > >> + > >> + return 0; > >> +} > >> + > >> +static int adv748x_afe_querystd(struct v4l2_subdev *sd, v4l2_std_id *std) > >> +{ > >> + struct adv748x_afe *afe = adv748x_sd_to_afe(sd); > >> + struct adv748x_state *state = adv748x_afe_to_state(afe); > >> + int ret; > >> + > >> + mutex_lock(&state->mutex); > >> + > >> + if (afe->streaming) { > >> + ret = -EBUSY; > >> + goto unlock; > >> + } > >> + > >> + /* Set auto detect mode */ > >> + adv748x_afe_set_video_standard(state, > >> + ADV748X_AFE_STD_AD_PAL_BG_NTSC_J_SECAM); > >> + > >> + msleep(100); > >> + > >> + /* Read detected standard */ > >> + ret = adv748x_afe_status(afe, NULL, std); > >> + > >> + /* Restore original state */ > >> + adv748x_afe_set_video_standard(state, afe->curr_norm); > >> + > >> +unlock: > >> + mutex_unlock(&state->mutex); > >> + > >> + return ret; > >> +} > >> + > >> +static int adv748x_afe_g_tvnorms(struct v4l2_subdev *sd, v4l2_std_id *norm) > >> +{ > >> + *norm = V4L2_STD_ALL; > >> + > >> + return 0; > >> +} > >> + > >> +static int adv748x_afe_g_input_status(struct v4l2_subdev *sd, u32 *status) > >> +{ > >> + struct adv748x_afe *afe = adv748x_sd_to_afe(sd); > >> + struct adv748x_state *state = adv748x_afe_to_state(afe); > >> + int ret; > >> + > >> + mutex_lock(&state->mutex); > >> + > >> + ret = adv748x_afe_status(afe, status, NULL); > >> + > >> + mutex_unlock(&state->mutex); > >> + return ret; > >> +} > >> + > >> +static int adv748x_afe_s_stream(struct v4l2_subdev *sd, int enable) > >> +{ > >> + struct adv748x_afe *afe = adv748x_sd_to_afe(sd); > >> + struct adv748x_state *state = adv748x_afe_to_state(afe); > >> + int ret, signal = V4L2_IN_ST_NO_SIGNAL; > >> + > >> + mutex_lock(&state->mutex); > >> + > >> + if (enable) { > >> + ret = adv748x_afe_s_input(afe, afe->input); > >> + if (ret) > >> + goto unlock; > >> + } > >> + > >> + ret = adv748x_txb_power(state, enable); > >> + if (ret) > >> + goto unlock; > >> + > >> + afe->streaming = enable; > >> + > >> + adv748x_afe_status(afe, &signal, NULL); > >> + if (signal != V4L2_IN_ST_NO_SIGNAL) > >> + adv_dbg(state, "Detected SDP signal\n"); > >> + else > >> + adv_dbg(state, "Couldn't detect SDP video signal\n"); > >> + > >> +unlock: > >> + mutex_unlock(&state->mutex); > >> + > >> + return ret; > >> +} > >> + > >> +static const struct v4l2_subdev_video_ops adv748x_afe_video_ops = { > >> + .g_std = adv748x_afe_g_std, > >> + .s_std = adv748x_afe_s_std, > >> + .querystd = adv748x_afe_querystd, > >> + .g_tvnorms = adv748x_afe_g_tvnorms, > >> + .g_input_status = adv748x_afe_g_input_status, > >> + .s_stream = adv748x_afe_s_stream, > >> + .g_pixelaspect = adv748x_afe_g_pixelaspect, > >> +}; > >> + > >> +/* ----------------------------------------------------------------------------- > >> + * v4l2_subdev_pad_ops > >> + */ > >> + > >> +static int adv748x_afe_propagate_pixelrate(struct adv748x_afe *afe) > >> +{ > >> + struct v4l2_subdev *tx; > >> + unsigned int width, height, fps; > >> + > >> + tx = adv748x_get_remote_sd(&afe->pads[ADV748X_AFE_SOURCE]); > >> + if (!tx) > >> + return -ENOLINK; > >> + > >> + width = 720; > >> + height = afe->curr_norm & V4L2_STD_525_60 ? 480 : 576; > >> + fps = afe->curr_norm & V4L2_STD_525_60 ? 30 : 25; > >> + > >> + return adv748x_csi2_set_pixelrate(tx, width * height * fps); > >> +} > >> + > >> +static int adv748x_afe_enum_mbus_code(struct v4l2_subdev *sd, > >> + struct v4l2_subdev_pad_config *cfg, > >> + struct v4l2_subdev_mbus_code_enum *code) > >> +{ > >> + if (code->index != 0) > >> + return -EINVAL; > >> + > >> + code->code = MEDIA_BUS_FMT_UYVY8_2X8; > >> + > >> + return 0; > >> +} > >> + > >> +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; > >> + > >> + /* It makes no sense to get the format of the analog sink pads */ > >> + 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_propagate_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; > >> + > >> + /* It makes no sense to get the format of the analog sink pads */ > >> + 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; > >> + > >> + return 0; > >> +} > >> + > >> +static const struct v4l2_subdev_pad_ops adv748x_afe_pad_ops = { > >> + .enum_mbus_code = adv748x_afe_enum_mbus_code, > >> + .set_fmt = adv748x_afe_set_format, > >> + .get_fmt = adv748x_afe_get_format, > >> +}; > >> + > >> +/* ----------------------------------------------------------------------------- > >> + * v4l2_subdev_ops > >> + */ > >> + > >> +static const struct v4l2_subdev_ops adv748x_afe_ops = { > >> + .video = &adv748x_afe_video_ops, > >> + .pad = &adv748x_afe_pad_ops, > >> +}; > >> + > >> +/* ----------------------------------------------------------------------------- > >> + * Controls > >> + */ > >> + > >> +static const char * const afe_ctrl_frp_menu[] = { > >> + "Disabled", > >> + "Solid Blue", > >> + "Color Bars", > >> + "Grey Ramp", > >> + "Cb Ramp", > >> + "Cr Ramp", > >> + "Boundary" > >> +}; > >> + > >> +static int adv748x_afe_s_ctrl(struct v4l2_ctrl *ctrl) > >> +{ > >> + struct adv748x_afe *afe = adv748x_ctrl_to_afe(ctrl); > >> + struct adv748x_state *state = adv748x_afe_to_state(afe); > >> + bool enable; > >> + int ret; > >> + > >> + ret = sdp_write(state, 0x0e, 0x00); > >> + if (ret < 0) > >> + return ret; > >> + > >> + switch (ctrl->id) { > >> + case V4L2_CID_BRIGHTNESS: > >> + ret = sdp_write(state, ADV748X_SDP_BRI, ctrl->val); > >> + break; > >> + case V4L2_CID_HUE: > >> + /* Hue is inverted according to HSL chart */ > >> + ret = sdp_write(state, ADV748X_SDP_HUE, -ctrl->val); > >> + break; > >> + case V4L2_CID_CONTRAST: > >> + ret = sdp_write(state, ADV748X_SDP_CON, ctrl->val); > >> + break; > >> + case V4L2_CID_SATURATION: > >> + ret = sdp_write(state, ADV748X_SDP_SD_SAT_U, ctrl->val); > >> + if (ret) > >> + break; > >> + ret = sdp_write(state, ADV748X_SDP_SD_SAT_V, ctrl->val); > >> + break; > >> + case V4L2_CID_TEST_PATTERN: > >> + enable = !!ctrl->val; > >> + > >> + /* Enable/Disable Color bar test patterns */ > >> + ret = sdp_clrset(state, ADV748X_SDP_DEF, ADV748X_SDP_DEF_VAL_EN, > >> + enable); > >> + if (ret) > >> + break; > >> + ret = sdp_clrset(state, ADV748X_SDP_FRP, ADV748X_SDP_FRP_MASK, > >> + enable ? ctrl->val - 1 : 0); > >> + break; > >> + default: > >> + return -EINVAL; > >> + } > >> + > >> + return ret; > >> +} > >> + > >> +static const struct v4l2_ctrl_ops adv748x_afe_ctrl_ops = { > >> + .s_ctrl = adv748x_afe_s_ctrl, > >> +}; > >> + > >> +static int adv748x_afe_init_controls(struct adv748x_afe *afe) > >> +{ > >> + struct adv748x_state *state = adv748x_afe_to_state(afe); > >> + > >> + v4l2_ctrl_handler_init(&afe->ctrl_hdl, 5); > >> + > >> + /* Use our mutex for the controls */ > >> + afe->ctrl_hdl.lock = &state->mutex; > >> + > >> + v4l2_ctrl_new_std(&afe->ctrl_hdl, &adv748x_afe_ctrl_ops, > >> + V4L2_CID_BRIGHTNESS, ADV748X_SDP_BRI_MIN, > >> + ADV748X_SDP_BRI_MAX, 1, ADV748X_SDP_BRI_DEF); > >> + v4l2_ctrl_new_std(&afe->ctrl_hdl, &adv748x_afe_ctrl_ops, > >> + V4L2_CID_CONTRAST, ADV748X_SDP_CON_MIN, > >> + ADV748X_SDP_CON_MAX, 1, ADV748X_SDP_CON_DEF); > >> + v4l2_ctrl_new_std(&afe->ctrl_hdl, &adv748x_afe_ctrl_ops, > >> + V4L2_CID_SATURATION, ADV748X_SDP_SAT_MIN, > >> + ADV748X_SDP_SAT_MAX, 1, ADV748X_SDP_SAT_DEF); > >> + v4l2_ctrl_new_std(&afe->ctrl_hdl, &adv748x_afe_ctrl_ops, > >> + V4L2_CID_HUE, ADV748X_SDP_HUE_MIN, > >> + ADV748X_SDP_HUE_MAX, 1, ADV748X_SDP_HUE_DEF); > >> + > >> + v4l2_ctrl_new_std_menu_items(&afe->ctrl_hdl, &adv748x_afe_ctrl_ops, > >> + V4L2_CID_TEST_PATTERN, > >> + ARRAY_SIZE(afe_ctrl_frp_menu) - 1, > >> + 0, 0, afe_ctrl_frp_menu); > >> + > >> + afe->sd.ctrl_handler = &afe->ctrl_hdl; > >> + if (afe->ctrl_hdl.error) { > >> + v4l2_ctrl_handler_free(&afe->ctrl_hdl); > >> + return afe->ctrl_hdl.error; > >> + } > >> + > >> + return v4l2_ctrl_handler_setup(&afe->ctrl_hdl); > >> +} > >> + > >> +int adv748x_afe_init(struct adv748x_afe *afe) > >> +{ > >> + struct adv748x_state *state = adv748x_afe_to_state(afe); > >> + int ret; > >> + unsigned int i; > >> + > >> + afe->input = 0; > >> + afe->streaming = false; > >> + afe->curr_norm = V4L2_STD_NTSC_M; > >> + > >> + adv748x_subdev_init(&afe->sd, state, &adv748x_afe_ops, > >> + MEDIA_ENT_F_ATV_DECODER, "afe"); > >> + > >> + /* Identify the first connector found as a default input if set */ > >> + for (i = ADV748X_PORT_AIN0; i <= ADV748X_PORT_AIN7; i++) { > >> + /* Inputs and ports are 1-indexed to match the data sheet */ > >> + if (state->endpoints[i]) { > >> + afe->input = i; > >> + break; > >> + } > >> + } > >> + > >> + adv748x_afe_s_input(afe, afe->input); > >> + > >> + adv_dbg(state, "AFE Default input set to %d\n", afe->input); > >> + > >> + /* Entity pads and sinks are 0-indexed to match the pads */ > >> + for (i = ADV748X_AFE_SINK_AIN0; i <= ADV748X_AFE_SINK_AIN7; i++) > >> + afe->pads[i].flags = MEDIA_PAD_FL_SINK; > >> + > >> + afe->pads[ADV748X_AFE_SOURCE].flags = MEDIA_PAD_FL_SOURCE; > >> + > >> + ret = media_entity_pads_init(&afe->sd.entity, ADV748X_AFE_NR_PADS, > >> + afe->pads); > >> + if (ret) > >> + return ret; > >> + > >> + ret = adv748x_afe_init_controls(afe); > >> + if (ret) > >> + goto error; > >> + > >> + return 0; > >> + > >> +error: > >> + media_entity_cleanup(&afe->sd.entity); > >> + > >> + return ret; > >> +} > >> + > >> +void adv748x_afe_cleanup(struct adv748x_afe *afe) > >> +{ > >> + v4l2_device_unregister_subdev(&afe->sd); > >> + media_entity_cleanup(&afe->sd.entity); > >> + v4l2_ctrl_handler_free(&afe->ctrl_hdl); > >> +} > >> diff --git a/drivers/media/i2c/adv748x/adv748x-core.c b/drivers/media/i2c/adv748x/adv748x-core.c > >> new file mode 100644 > >> index 000000000000..aeb6ae80cb18 > >> --- /dev/null > >> +++ b/drivers/media/i2c/adv748x/adv748x-core.c > >> @@ -0,0 +1,832 @@ > >> +/* > >> + * Driver for Analog Devices ADV748X HDMI receiver with AFE > >> + * > >> + * Copyright (C) 2017 Renesas Electronics Corp. > >> + * > >> + * This program is free software; you can redistribute it and/or modify it > >> + * under the terms of the GNU General Public License as published by the > >> + * Free Software Foundation; either version 2 of the License, or (at your > >> + * option) any later version. > >> + * > >> + * Authors: > >> + * Koji Matsuoka <koji.matsuoka.xm@xxxxxxxxxxx> > >> + * Niklas Söderlund <niklas.soderlund@xxxxxxxxxxxx> > >> + * Kieran Bingham <kieran.bingham@xxxxxxxxxxxxxxxx> > >> + */ > >> + > >> +#include <linux/delay.h> > >> +#include <linux/errno.h> > >> +#include <linux/i2c.h> > >> +#include <linux/module.h> > >> +#include <linux/mutex.h> > >> +#include <linux/of_graph.h> > >> +#include <linux/regmap.h> > >> +#include <linux/slab.h> > >> +#include <linux/v4l2-dv-timings.h> > >> + > >> +#include <media/v4l2-ctrls.h> > >> +#include <media/v4l2-device.h> > >> +#include <media/v4l2-dv-timings.h> > >> +#include <media/v4l2-ioctl.h> > >> + > >> +#include "adv748x.h" > >> + > >> +/* ----------------------------------------------------------------------------- > >> + * Register manipulation > >> + */ > >> + > >> +static const struct regmap_config adv748x_regmap_cnf[] = { > >> + { > >> + .name = "io", > >> + .reg_bits = 8, > >> + .val_bits = 8, > >> + > >> + .max_register = 0xff, > >> + .cache_type = REGCACHE_NONE, > >> + }, > >> + { > >> + .name = "dpll", > >> + .reg_bits = 8, > >> + .val_bits = 8, > >> + > >> + .max_register = 0xff, > >> + .cache_type = REGCACHE_NONE, > >> + }, > >> + { > >> + .name = "cp", > >> + .reg_bits = 8, > >> + .val_bits = 8, > >> + > >> + .max_register = 0xff, > >> + .cache_type = REGCACHE_NONE, > >> + }, > >> + { > >> + .name = "hdmi", > >> + .reg_bits = 8, > >> + .val_bits = 8, > >> + > >> + .max_register = 0xff, > >> + .cache_type = REGCACHE_NONE, > >> + }, > >> + { > >> + .name = "edid", > >> + .reg_bits = 8, > >> + .val_bits = 8, > >> + > >> + .max_register = 0xff, > >> + .cache_type = REGCACHE_NONE, > >> + }, > >> + { > >> + .name = "repeater", > >> + .reg_bits = 8, > >> + .val_bits = 8, > >> + > >> + .max_register = 0xff, > >> + .cache_type = REGCACHE_NONE, > >> + }, > >> + { > >> + .name = "infoframe", > >> + .reg_bits = 8, > >> + .val_bits = 8, > >> + > >> + .max_register = 0xff, > >> + .cache_type = REGCACHE_NONE, > >> + }, > >> + { > >> + .name = "cec", > >> + .reg_bits = 8, > >> + .val_bits = 8, > >> + > >> + .max_register = 0xff, > >> + .cache_type = REGCACHE_NONE, > >> + }, > >> + { > >> + .name = "sdp", > >> + .reg_bits = 8, > >> + .val_bits = 8, > >> + > >> + .max_register = 0xff, > >> + .cache_type = REGCACHE_NONE, > >> + }, > >> + > >> + { > >> + .name = "txb", > >> + .reg_bits = 8, > >> + .val_bits = 8, > >> + > >> + .max_register = 0xff, > >> + .cache_type = REGCACHE_NONE, > >> + }, > >> + { > >> + .name = "txa", > >> + .reg_bits = 8, > >> + .val_bits = 8, > >> + > >> + .max_register = 0xff, > >> + .cache_type = REGCACHE_NONE, > >> + }, > >> +}; > >> + > >> +static int adv748x_configure_regmap(struct adv748x_state *state, int region) > >> +{ > >> + int err; > >> + > >> + if (!state->i2c_clients[region]) > >> + return -ENODEV; > >> + > >> + state->regmap[region] = > >> + devm_regmap_init_i2c(state->i2c_clients[region], > >> + &adv748x_regmap_cnf[region]); > >> + > >> + if (IS_ERR(state->regmap[region])) { > >> + err = PTR_ERR(state->regmap[region]); > >> + adv_err(state, > >> + "Error initializing regmap %d with error %d\n", > >> + region, err); > >> + return -EINVAL; > >> + } > >> + > >> + return 0; > >> +} > >> + > >> +/* Default addresses for the I2C pages */ > >> +static int adv748x_i2c_addresses[ADV748X_PAGE_MAX] = { > >> + ADV748X_I2C_IO, > >> + ADV748X_I2C_DPLL, > >> + ADV748X_I2C_CP, > >> + ADV748X_I2C_HDMI, > >> + ADV748X_I2C_EDID, > >> + ADV748X_I2C_REPEATER, > >> + ADV748X_I2C_INFOFRAME, > >> + ADV748X_I2C_CEC, > >> + ADV748X_I2C_SDP, > >> + ADV748X_I2C_TXB, > >> + ADV748X_I2C_TXA, > >> +}; > >> + > >> +static int adv748x_read_check(struct adv748x_state *state, > >> + int client_page, u8 reg) > >> +{ > >> + struct i2c_client *client = state->i2c_clients[client_page]; > >> + int err; > >> + unsigned int val; > >> + > >> + err = regmap_read(state->regmap[client_page], reg, &val); > >> + > >> + if (err) { > >> + adv_err(state, "error reading %02x, %02x\n", > >> + client->addr, reg); > >> + return err; > >> + } > >> + > >> + return val; > >> +} > >> + > >> +int adv748x_read(struct adv748x_state *state, u8 page, u8 reg) > >> +{ > >> + return adv748x_read_check(state, page, reg); > >> +} > >> + > >> +int adv748x_write(struct adv748x_state *state, u8 page, u8 reg, u8 value) > >> +{ > >> + return regmap_write(state->regmap[page], reg, value); > >> +} > >> + > >> +/* adv748x_write_block(): Write raw data with a maximum of I2C_SMBUS_BLOCK_MAX > >> + * size to one or more registers. > >> + * > >> + * A value of zero will be returned on success, a negative errno will > >> + * be returned in error cases. > >> + */ > >> +int adv748x_write_block(struct adv748x_state *state, int client_page, > >> + unsigned int init_reg, const void *val, > >> + size_t val_len) > >> +{ > >> + struct regmap *regmap = state->regmap[client_page]; > >> + > >> + if (val_len > I2C_SMBUS_BLOCK_MAX) > >> + val_len = I2C_SMBUS_BLOCK_MAX; > >> + > >> + return regmap_raw_write(regmap, init_reg, val, val_len); > >> +} > >> + > >> +static struct i2c_client *adv748x_dummy_client(struct adv748x_state *state, > >> + u8 addr, u8 io_reg) > >> +{ > >> + struct i2c_client *client = state->client; > >> + > >> + if (addr) > >> + io_write(state, io_reg, addr << 1); > >> + > >> + return i2c_new_dummy(client->adapter, io_read(state, io_reg) >> 1); > >> +} > >> + > >> +static void adv748x_unregister_clients(struct adv748x_state *state) > >> +{ > >> + unsigned int i; > >> + > >> + for (i = 1; i < ARRAY_SIZE(state->i2c_clients); ++i) { > >> + if (state->i2c_clients[i]) > >> + i2c_unregister_device(state->i2c_clients[i]); > >> + } > >> +} > >> + > >> +static int adv748x_initialise_clients(struct adv748x_state *state) > >> +{ > >> + int i; > >> + int ret; > >> + > >> + for (i = ADV748X_PAGE_DPLL; i < ADV748X_PAGE_MAX; ++i) { > >> + state->i2c_clients[i] = > >> + adv748x_dummy_client(state, adv748x_i2c_addresses[i], > >> + ADV748X_IO_SLAVE_ADDR_BASE + i); > >> + if (state->i2c_clients[i] == NULL) { > >> + adv_err(state, "failed to create i2c client %u\n", i); > >> + return -ENOMEM; > >> + } > >> + > >> + ret = adv748x_configure_regmap(state, i); > >> + if (ret) > >> + return ret; > >> + } > >> + > >> + return 0; > >> +} > >> + > >> +/** > >> + * struct adv748x_reg_value - Register write instruction > >> + * @page: Regmap page identifier > >> + * @reg: I2C register > >> + * @value: value to write to @page at @reg > >> + */ > >> +struct adv748x_reg_value { > >> + u8 page; > >> + u8 reg; > >> + u8 value; > >> +}; > >> + > >> +static int adv748x_write_regs(struct adv748x_state *state, > >> + const struct adv748x_reg_value *regs) > >> +{ > >> + int ret; > >> + > >> + while (regs->page != ADV748X_PAGE_EOR) { > >> + if (regs->page == ADV748X_PAGE_WAIT) { > >> + msleep(regs->value); > >> + } else { > >> + ret = adv748x_write(state, regs->page, regs->reg, > >> + regs->value); > >> + if (ret < 0) { > >> + adv_err(state, > >> + "Error regs page: 0x%02x reg: 0x%02x\n", > >> + regs->page, regs->reg); > >> + return ret; > >> + } > >> + } > >> + regs++; > >> + } > >> + > >> + return 0; > >> +} > >> + > >> +/* ----------------------------------------------------------------------------- > >> + * TXA and TXB > >> + */ > >> + > >> +static const struct adv748x_reg_value adv748x_power_up_txa_4lane[] = { > >> + > >> + {ADV748X_PAGE_TXA, 0x00, 0x84}, /* Enable 4-lane MIPI */ > >> + {ADV748X_PAGE_TXA, 0x00, 0xa4}, /* Set Auto DPHY Timing */ > >> + > >> + {ADV748X_PAGE_TXA, 0x31, 0x82}, /* ADI Required Write */ > >> + {ADV748X_PAGE_TXA, 0x1e, 0x40}, /* ADI Required Write */ > >> + {ADV748X_PAGE_TXA, 0xda, 0x01}, /* i2c_mipi_pll_en - 1'b1 */ > >> + {ADV748X_PAGE_WAIT, 0x00, 0x02},/* delay 2 */ > >> + {ADV748X_PAGE_TXA, 0x00, 0x24 },/* Power-up CSI-TX */ > >> + {ADV748X_PAGE_WAIT, 0x00, 0x01},/* delay 1 */ > >> + {ADV748X_PAGE_TXA, 0xc1, 0x2b}, /* ADI Required Write */ > >> + {ADV748X_PAGE_WAIT, 0x00, 0x01},/* delay 1 */ > >> + {ADV748X_PAGE_TXA, 0x31, 0x80}, /* ADI Required Write */ > >> + > >> + {ADV748X_PAGE_EOR, 0xff, 0xff} /* End of register table */ > >> +}; > >> + > >> +static const struct adv748x_reg_value adv748x_power_down_txa_4lane[] = { > >> + > >> + {ADV748X_PAGE_TXA, 0x31, 0x82}, /* ADI Required Write */ > >> + {ADV748X_PAGE_TXA, 0x1e, 0x00}, /* ADI Required Write */ > >> + {ADV748X_PAGE_TXA, 0x00, 0x84}, /* Enable 4-lane MIPI */ > >> + {ADV748X_PAGE_TXA, 0xda, 0x01}, /* i2c_mipi_pll_en - 1'b1 */ > >> + {ADV748X_PAGE_TXA, 0xc1, 0x3b}, /* ADI Required Write */ > >> + > >> + {ADV748X_PAGE_EOR, 0xff, 0xff} /* End of register table */ > >> +}; > >> + > >> +static const struct adv748x_reg_value adv748x_power_up_txb_1lane[] = { > >> + > >> + {ADV748X_PAGE_TXB, 0x00, 0x81}, /* Enable 1-lane MIPI */ > >> + {ADV748X_PAGE_TXB, 0x00, 0xa1}, /* Set Auto DPHY Timing */ > >> + > >> + {ADV748X_PAGE_TXB, 0x31, 0x82}, /* ADI Required Write */ > >> + {ADV748X_PAGE_TXB, 0x1e, 0x40}, /* ADI Required Write */ > >> + {ADV748X_PAGE_TXB, 0xda, 0x01}, /* i2c_mipi_pll_en - 1'b1 */ > >> + {ADV748X_PAGE_WAIT, 0x00, 0x02},/* delay 2 */ > >> + {ADV748X_PAGE_TXB, 0x00, 0x21 },/* Power-up CSI-TX */ > >> + {ADV748X_PAGE_WAIT, 0x00, 0x01},/* delay 1 */ > >> + {ADV748X_PAGE_TXB, 0xc1, 0x2b}, /* ADI Required Write */ > >> + {ADV748X_PAGE_WAIT, 0x00, 0x01},/* delay 1 */ > >> + {ADV748X_PAGE_TXB, 0x31, 0x80}, /* ADI Required Write */ > >> + > >> + {ADV748X_PAGE_EOR, 0xff, 0xff} /* End of register table */ > >> +}; > >> + > >> +static const struct adv748x_reg_value adv748x_power_down_txb_1lane[] = { > >> + > >> + {ADV748X_PAGE_TXB, 0x31, 0x82}, /* ADI Required Write */ > >> + {ADV748X_PAGE_TXB, 0x1e, 0x00}, /* ADI Required Write */ > >> + {ADV748X_PAGE_TXB, 0x00, 0x81}, /* Enable 4-lane MIPI */ > >> + {ADV748X_PAGE_TXB, 0xda, 0x01}, /* i2c_mipi_pll_en - 1'b1 */ > >> + {ADV748X_PAGE_TXB, 0xc1, 0x3b}, /* ADI Required Write */ > >> + > >> + {ADV748X_PAGE_EOR, 0xff, 0xff} /* End of register table */ > >> +}; > >> + > >> +int adv748x_txa_power(struct adv748x_state *state, bool on) > >> +{ > >> + int val; > >> + > >> + val = txa_read(state, ADV748X_CSI_FS_AS_LS); > >> + if (val < 0) > >> + return val; > >> + > >> + /* > >> + * This test against BIT(6) is not documented by the datasheet, but was > >> + * specified in the downstream driver. > >> + * Track with a WARN_ONCE to determine if it is ever set by HW. > >> + */ > >> + WARN_ONCE((on && val & ADV748X_CSI_FS_AS_LS_UNKNOWN), > >> + "Enabling with unknown bit set"); > >> + > >> + if (on) > >> + return adv748x_write_regs(state, adv748x_power_up_txa_4lane); > >> + > >> + return adv748x_write_regs(state, adv748x_power_down_txa_4lane); > >> +} > >> + > >> +int adv748x_txb_power(struct adv748x_state *state, bool on) > >> +{ > >> + int val; > >> + > >> + val = txb_read(state, ADV748X_CSI_FS_AS_LS); > >> + if (val < 0) > >> + return val; > >> + > >> + /* > >> + * This test against BIT(6) is not documented by the datasheet, but was > >> + * specified in the downstream driver. > >> + * Track with a WARN_ONCE to determine if it is ever set by HW. > >> + */ > >> + WARN_ONCE((on && val & ADV748X_CSI_FS_AS_LS_UNKNOWN), > >> + "Enabling with unknown bit set"); > >> + > >> + if (on) > >> + return adv748x_write_regs(state, adv748x_power_up_txb_1lane); > >> + > >> + return adv748x_write_regs(state, adv748x_power_down_txb_1lane); > >> +} > >> + > >> +/* ----------------------------------------------------------------------------- > >> + * Media Operations > >> + */ > >> + > >> +static const struct media_entity_operations adv748x_media_ops = { > >> + .link_validate = v4l2_subdev_link_validate, > >> +}; > >> + > >> +/* ----------------------------------------------------------------------------- > >> + * HW setup > >> + */ > >> + > >> +static const struct adv748x_reg_value adv748x_sw_reset[] = { > >> + > >> + {ADV748X_PAGE_IO, 0xff, 0xff}, /* SW reset */ > >> + {ADV748X_PAGE_WAIT, 0x00, 0x05},/* delay 5 */ > >> + {ADV748X_PAGE_IO, 0x01, 0x76}, /* ADI Required Write */ > >> + {ADV748X_PAGE_IO, 0xf2, 0x01}, /* Enable I2C Read Auto-Increment */ > >> + {ADV748X_PAGE_EOR, 0xff, 0xff} /* End of register table */ > >> +}; > >> + > >> +static const struct adv748x_reg_value adv748x_set_slave_address[] = { > >> + {ADV748X_PAGE_IO, 0xf3, ADV748X_I2C_DPLL << 1}, > >> + {ADV748X_PAGE_IO, 0xf4, ADV748X_I2C_CP << 1}, > >> + {ADV748X_PAGE_IO, 0xf5, ADV748X_I2C_HDMI << 1}, > >> + {ADV748X_PAGE_IO, 0xf6, ADV748X_I2C_EDID << 1}, > >> + {ADV748X_PAGE_IO, 0xf7, ADV748X_I2C_REPEATER << 1}, > >> + {ADV748X_PAGE_IO, 0xf8, ADV748X_I2C_INFOFRAME << 1}, > >> + {ADV748X_PAGE_IO, 0xfa, ADV748X_I2C_CEC << 1}, > >> + {ADV748X_PAGE_IO, 0xfb, ADV748X_I2C_SDP << 1}, > >> + {ADV748X_PAGE_IO, 0xfc, ADV748X_I2C_TXB << 1}, > >> + {ADV748X_PAGE_IO, 0xfd, ADV748X_I2C_TXA << 1}, > >> + {ADV748X_PAGE_EOR, 0xff, 0xff} /* End of register table */ > >> +}; > >> + > >> +/* Supported Formats For Script Below */ > >> +/* - 01-29 HDMI to MIPI TxA CSI 4-Lane - RGB888: */ > >> +static const struct adv748x_reg_value adv748x_init_txa_4lane[] = { > >> + /* Disable chip powerdown & Enable HDMI Rx block */ > >> + {ADV748X_PAGE_IO, 0x00, 0x40}, > >> + > >> + {ADV748X_PAGE_REPEATER, 0x40, 0x83}, /* Enable HDCP 1.1 */ > >> + > >> + {ADV748X_PAGE_HDMI, 0x00, 0x08},/* Foreground Channel = A */ > >> + {ADV748X_PAGE_HDMI, 0x98, 0xff},/* ADI Required Write */ > >> + {ADV748X_PAGE_HDMI, 0x99, 0xa3},/* ADI Required Write */ > >> + {ADV748X_PAGE_HDMI, 0x9a, 0x00},/* ADI Required Write */ > >> + {ADV748X_PAGE_HDMI, 0x9b, 0x0a},/* ADI Required Write */ > >> + {ADV748X_PAGE_HDMI, 0x9d, 0x40},/* ADI Required Write */ > >> + {ADV748X_PAGE_HDMI, 0xcb, 0x09},/* ADI Required Write */ > >> + {ADV748X_PAGE_HDMI, 0x3d, 0x10},/* ADI Required Write */ > >> + {ADV748X_PAGE_HDMI, 0x3e, 0x7b},/* ADI Required Write */ > >> + {ADV748X_PAGE_HDMI, 0x3f, 0x5e},/* ADI Required Write */ > >> + {ADV748X_PAGE_HDMI, 0x4e, 0xfe},/* ADI Required Write */ > >> + {ADV748X_PAGE_HDMI, 0x4f, 0x18},/* ADI Required Write */ > >> + {ADV748X_PAGE_HDMI, 0x57, 0xa3},/* ADI Required Write */ > >> + {ADV748X_PAGE_HDMI, 0x58, 0x04},/* ADI Required Write */ > >> + {ADV748X_PAGE_HDMI, 0x85, 0x10},/* ADI Required Write */ > >> + > >> + {ADV748X_PAGE_HDMI, 0x83, 0x00},/* Enable All Terminations */ > >> + {ADV748X_PAGE_HDMI, 0xa3, 0x01},/* ADI Required Write */ > >> + {ADV748X_PAGE_HDMI, 0xbe, 0x00},/* ADI Required Write */ > >> + > >> + {ADV748X_PAGE_HDMI, 0x6c, 0x01},/* HPA Manual Enable */ > >> + {ADV748X_PAGE_HDMI, 0xf8, 0x01},/* HPA Asserted */ > >> + {ADV748X_PAGE_HDMI, 0x0f, 0x00},/* Audio Mute Speed Set to Fastest */ > >> + /* (Smallest Step Size) */ > >> + > >> + {ADV748X_PAGE_IO, 0x04, 0x02}, /* RGB Out of CP */ > >> + {ADV748X_PAGE_IO, 0x12, 0xf0}, /* CSC Depends on ip Packets, SDR 444 */ > >> + {ADV748X_PAGE_IO, 0x17, 0x80}, /* Luma & Chroma can reach 254d */ > >> + {ADV748X_PAGE_IO, 0x03, 0x86}, /* CP-Insert_AV_Code */ > >> + > >> + {ADV748X_PAGE_CP, 0x7c, 0x00}, /* ADI Required Write */ > >> + > >> + {ADV748X_PAGE_IO, 0x0c, 0xe0}, /* Enable LLC_DLL & Double LLC Timing */ > >> + {ADV748X_PAGE_IO, 0x0e, 0xdd}, /* LLC/PIX/SPI PINS TRISTATED AUD */ > >> + /* Outputs Enabled */ > >> + {ADV748X_PAGE_IO, 0x10, 0xa0}, /* Enable 4-lane CSI Tx & Pixel Port */ > >> + > >> + {ADV748X_PAGE_TXA, 0x00, 0x84}, /* Enable 4-lane MIPI */ > >> + {ADV748X_PAGE_TXA, 0x00, 0xa4}, /* Set Auto DPHY Timing */ > >> + {ADV748X_PAGE_TXA, 0xdb, 0x10}, /* ADI Required Write */ > >> + {ADV748X_PAGE_TXA, 0xd6, 0x07}, /* ADI Required Write */ > >> + {ADV748X_PAGE_TXA, 0xc4, 0x0a}, /* ADI Required Write */ > >> + {ADV748X_PAGE_TXA, 0x71, 0x33}, /* ADI Required Write */ > >> + {ADV748X_PAGE_TXA, 0x72, 0x11}, /* ADI Required Write */ > >> + {ADV748X_PAGE_TXA, 0xf0, 0x00}, /* i2c_dphy_pwdn - 1'b0 */ > >> + > >> + {ADV748X_PAGE_TXA, 0x31, 0x82}, /* ADI Required Write */ > >> + {ADV748X_PAGE_TXA, 0x1e, 0x40}, /* ADI Required Write */ > >> + {ADV748X_PAGE_TXA, 0xda, 0x01}, /* i2c_mipi_pll_en - 1'b1 */ > >> + {ADV748X_PAGE_WAIT, 0x00, 0x02},/* delay 2 */ > >> + {ADV748X_PAGE_TXA, 0x00, 0x24 },/* Power-up CSI-TX */ > >> + {ADV748X_PAGE_WAIT, 0x00, 0x01},/* delay 1 */ > >> + {ADV748X_PAGE_TXA, 0xc1, 0x2b}, /* ADI Required Write */ > >> + {ADV748X_PAGE_WAIT, 0x00, 0x01},/* delay 1 */ > >> + {ADV748X_PAGE_TXA, 0x31, 0x80}, /* ADI Required Write */ > >> + > >> + {ADV748X_PAGE_EOR, 0xff, 0xff} /* End of register table */ > >> +}; > >> + > >> +/* 02-01 Analog CVBS to MIPI TX-B CSI 1-Lane - */ > >> +/* Autodetect CVBS Single Ended In Ain 1 - MIPI Out */ > >> +static const struct adv748x_reg_value adv748x_init_txb_1lane[] = { > >> + > >> + {ADV748X_PAGE_IO, 0x00, 0x30}, /* Disable chip powerdown Rx */ > >> + {ADV748X_PAGE_IO, 0xf2, 0x01}, /* Enable I2C Read Auto-Increment */ > >> + > >> + {ADV748X_PAGE_IO, 0x0e, 0xff}, /* LLC/PIX/AUD/SPI PINS TRISTATED */ > >> + > >> + {ADV748X_PAGE_SDP, 0x0f, 0x00}, /* Exit Power Down Mode */ > >> + {ADV748X_PAGE_SDP, 0x52, 0xcd}, /* ADI Required Write */ > >> + > >> + {ADV748X_PAGE_SDP, 0x0e, 0x80}, /* ADI Required Write */ > >> + {ADV748X_PAGE_SDP, 0x9c, 0x00}, /* ADI Required Write */ > >> + {ADV748X_PAGE_SDP, 0x9c, 0xff}, /* ADI Required Write */ > >> + {ADV748X_PAGE_SDP, 0x0e, 0x00}, /* ADI Required Write */ > >> + > >> + /* ADI recommended writes for improved video quality */ > >> + {ADV748X_PAGE_SDP, 0x80, 0x51}, /* ADI Required Write */ > >> + {ADV748X_PAGE_SDP, 0x81, 0x51}, /* ADI Required Write */ > >> + {ADV748X_PAGE_SDP, 0x82, 0x68}, /* ADI Required Write */ > >> + > >> + {ADV748X_PAGE_SDP, 0x03, 0x42}, /* Tri-S Output , PwrDwn 656 pads */ > >> + {ADV748X_PAGE_SDP, 0x04, 0xb5}, /* ITU-R BT.656-4 compatible */ > >> + {ADV748X_PAGE_SDP, 0x13, 0x00}, /* ADI Required Write */ > >> + > >> + {ADV748X_PAGE_SDP, 0x17, 0x41}, /* Select SH1 */ > >> + {ADV748X_PAGE_SDP, 0x31, 0x12}, /* ADI Required Write */ > >> + {ADV748X_PAGE_SDP, 0xe6, 0x4f}, /* V bit end pos manually in NTSC */ > >> + > >> + /* Enable 1-Lane MIPI Tx, */ > >> + /* enable pixel output and route SD through Pixel port */ > >> + {ADV748X_PAGE_IO, 0x10, 0x70}, > >> + > >> + {ADV748X_PAGE_TXB, 0x00, 0x81}, /* Enable 1-lane MIPI */ > >> + {ADV748X_PAGE_TXB, 0x00, 0xa1}, /* Set Auto DPHY Timing */ > >> + {ADV748X_PAGE_TXB, 0xd2, 0x40}, /* ADI Required Write */ > >> + {ADV748X_PAGE_TXB, 0xc4, 0x0a}, /* ADI Required Write */ > >> + {ADV748X_PAGE_TXB, 0x71, 0x33}, /* ADI Required Write */ > >> + {ADV748X_PAGE_TXB, 0x72, 0x11}, /* ADI Required Write */ > >> + {ADV748X_PAGE_TXB, 0xf0, 0x00}, /* i2c_dphy_pwdn - 1'b0 */ > >> + {ADV748X_PAGE_TXB, 0x31, 0x82}, /* ADI Required Write */ > >> + {ADV748X_PAGE_TXB, 0x1e, 0x40}, /* ADI Required Write */ > >> + {ADV748X_PAGE_TXB, 0xda, 0x01}, /* i2c_mipi_pll_en - 1'b1 */ > >> + > >> + {ADV748X_PAGE_WAIT, 0x00, 0x02},/* delay 2 */ > >> + {ADV748X_PAGE_TXB, 0x00, 0x21 },/* Power-up CSI-TX */ > >> + {ADV748X_PAGE_WAIT, 0x00, 0x01},/* delay 1 */ > >> + {ADV748X_PAGE_TXB, 0xc1, 0x2b}, /* ADI Required Write */ > >> + {ADV748X_PAGE_WAIT, 0x00, 0x01},/* delay 1 */ > >> + {ADV748X_PAGE_TXB, 0x31, 0x80}, /* ADI Required Write */ > >> + > >> + {ADV748X_PAGE_EOR, 0xff, 0xff} /* End of register table */ > >> +}; > >> + > >> +static int adv748x_reset(struct adv748x_state *state) > >> +{ > >> + int ret; > >> + > >> + ret = adv748x_write_regs(state, adv748x_sw_reset); > >> + if (ret < 0) > >> + return ret; > >> + > >> + ret = adv748x_write_regs(state, adv748x_set_slave_address); > >> + if (ret < 0) > >> + return ret; > >> + > >> + /* Init and power down TXA */ > >> + ret = adv748x_write_regs(state, adv748x_init_txa_4lane); > >> + if (ret) > >> + return ret; > >> + > >> + adv748x_txa_power(state, 0); > >> + > >> + /* Init and power down TXB */ > >> + ret = adv748x_write_regs(state, adv748x_init_txb_1lane); > >> + if (ret) > >> + return ret; > >> + > >> + adv748x_txb_power(state, 0); > >> + > >> + /* Disable chip powerdown & Enable HDMI Rx block */ > >> + io_write(state, ADV748X_IO_PD, ADV748X_IO_PD_RX_EN); > >> + > >> + /* Enable 4-lane CSI Tx & Pixel Port */ > >> + io_write(state, ADV748X_IO_10, ADV748X_IO_10_CSI4_EN | > >> + ADV748X_IO_10_CSI1_EN | > >> + ADV748X_IO_10_PIX_OUT_EN); > >> + > >> + /* Use vid_std and v_freq as freerun resolution for CP */ > >> + cp_clrset(state, ADV748X_CP_CLMP_POS, ADV748X_CP_CLMP_POS_DIS_AUTO, > >> + ADV748X_CP_CLMP_POS_DIS_AUTO); > >> + > >> + return 0; > >> +} > >> + > >> +static int adv748x_identify_chip(struct adv748x_state *state) > >> +{ > >> + int msb, lsb; > >> + > >> + lsb = io_read(state, ADV748X_IO_CHIP_REV_ID_1); > >> + msb = io_read(state, ADV748X_IO_CHIP_REV_ID_2); > >> + > >> + if (lsb < 0 || msb < 0) { > >> + adv_err(state, "Failed to read chip revision\n"); > >> + return -EIO; > >> + } > >> + > >> + adv_info(state, "chip found @ 0x%02x revision %02x%02x\n", > >> + state->client->addr << 1, lsb, msb); > >> + > >> + return 0; > >> +} > >> + > >> +/* ----------------------------------------------------------------------------- > >> + * i2c driver > >> + */ > >> + > >> +void adv748x_subdev_init(struct v4l2_subdev *sd, struct adv748x_state *state, > >> + const struct v4l2_subdev_ops *ops, u32 function, > >> + const char *ident) > >> +{ > >> + v4l2_subdev_init(sd, ops); > >> + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; > >> + > >> + /* the owner is the same as the i2c_client's driver owner */ > >> + sd->owner = state->dev->driver->owner; > >> + sd->dev = state->dev; > >> + > >> + v4l2_set_subdevdata(sd, state); > >> + > >> + /* initialize name */ > >> + snprintf(sd->name, sizeof(sd->name), "%s %d-%04x %s", > >> + state->dev->driver->name, > >> + i2c_adapter_id(state->client->adapter), > >> + state->client->addr, ident); > >> + > >> + sd->entity.function = function; > >> + sd->entity.ops = &adv748x_media_ops; > >> +} > >> + > >> +static int adv748x_parse_dt(struct adv748x_state *state) > >> +{ > >> + struct device_node *ep_np = NULL; > >> + struct of_endpoint ep; > >> + bool found = false; > >> + > >> + for_each_endpoint_of_node(state->dev->of_node, ep_np) { > >> + of_graph_parse_endpoint(ep_np, &ep); > >> + adv_info(state, "Endpoint %s on port %d", > >> + of_node_full_name(ep.local_node), > >> + ep.port); > >> + > >> + if (ep.port >= ADV748X_PORT_MAX) { > >> + adv_err(state, "Invalid endpoint %s on port %d", > >> + of_node_full_name(ep.local_node), > >> + ep.port); > >> + > >> + continue; > >> + } > >> + > >> + if (state->endpoints[ep.port]) { > >> + adv_err(state, > >> + "Multiple port endpoints are not supported"); > >> + continue; > >> + } > >> + > >> + of_node_get(ep_np); > >> + state->endpoints[ep.port] = ep_np; > >> + > >> + found = true; > >> + } > >> + > >> + return found ? 0 : -ENODEV; > >> +} > >> + > >> +static void adv748x_dt_cleanup(struct adv748x_state *state) > >> +{ > >> + unsigned int i; > >> + > >> + for (i = 0; i < ADV748X_PORT_MAX; i++) > >> + of_node_put(state->endpoints[i]); > >> +} > >> + > >> +static int adv748x_probe(struct i2c_client *client, > >> + const struct i2c_device_id *id) > >> +{ > >> + struct adv748x_state *state; > >> + int ret; > >> + > >> + /* Check if the adapter supports the needed features */ > >> + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) > >> + return -EIO; > >> + > >> + state = kzalloc(sizeof(struct adv748x_state), GFP_KERNEL); > >> + if (!state) > >> + return -ENOMEM; > >> + > >> + mutex_init(&state->mutex); > >> + > >> + state->dev = &client->dev; > >> + state->client = client; > >> + state->i2c_clients[ADV748X_PAGE_IO] = client; > >> + i2c_set_clientdata(client, state); > >> + > >> + /* Discover and process ports declared by the Device tree endpoints */ > >> + ret = adv748x_parse_dt(state); > >> + if (ret) { > >> + adv_err(state, "Failed to parse device tree"); > >> + goto err_free_mutex; > >> + } > >> + > >> + /* Configure IO Regmap region */ > >> + ret = adv748x_configure_regmap(state, ADV748X_PAGE_IO); > >> + if (ret) { > >> + adv_err(state, "Error configuring IO regmap region"); > >> + goto err_cleanup_dt; > >> + } > >> + > >> + ret = adv748x_identify_chip(state); > >> + if (ret) { > >> + adv_err(state, "Failed to identify chip"); > >> + goto err_cleanup_clients; > >> + } > >> + > >> + /* Configure remaining pages as I2C clients with regmap access */ > >> + ret = adv748x_initialise_clients(state); > >> + if (ret) { > >> + adv_err(state, "Failed to setup client regmap pages"); > >> + goto err_cleanup_clients; > >> + } > >> + > >> + /* SW reset ADV748X to its default values */ > >> + ret = adv748x_reset(state); > >> + if (ret) { > >> + adv_err(state, "Failed to reset hardware"); > >> + goto err_cleanup_clients; > >> + } > >> + > >> + /* Initialise HDMI */ > >> + ret = adv748x_hdmi_init(&state->hdmi); > >> + if (ret) { > >> + adv_err(state, "Failed to probe HDMI"); > >> + goto err_cleanup_clients; > >> + } > >> + > >> + /* Initialise AFE */ > >> + ret = adv748x_afe_init(&state->afe); > >> + if (ret) { > >> + adv_err(state, "Failed to probe AFE"); > >> + goto err_cleanup_hdmi; > >> + } > >> + > >> + /* Initialise TXA */ > >> + ret = adv748x_csi2_init(state, &state->txa); > >> + if (ret) { > >> + adv_err(state, "Failed to probe TXA"); > >> + goto err_cleanup_afe; > >> + } > >> + > >> + /* Initialise TXB */ > >> + ret = adv748x_csi2_init(state, &state->txb); > >> + if (ret) { > >> + adv_err(state, "Failed to probe TXB"); > >> + goto err_cleanup_txa; > >> + } > >> + > >> + return 0; > >> + > >> +err_cleanup_txa: > >> + adv748x_csi2_cleanup(&state->txa); > >> +err_cleanup_afe: > >> + adv748x_afe_cleanup(&state->afe); > >> +err_cleanup_hdmi: > >> + adv748x_hdmi_cleanup(&state->hdmi); > >> +err_cleanup_clients: > >> + adv748x_unregister_clients(state); > >> +err_cleanup_dt: > >> + adv748x_dt_cleanup(state); > >> +err_free_mutex: > >> + mutex_destroy(&state->mutex); > >> + kfree(state); > >> + > >> + return ret; > >> +} > >> + > >> +static int adv748x_remove(struct i2c_client *client) > >> +{ > >> + struct adv748x_state *state = i2c_get_clientdata(client); > >> + > >> + adv748x_afe_cleanup(&state->afe); > >> + adv748x_hdmi_cleanup(&state->hdmi); > >> + > >> + adv748x_csi2_cleanup(&state->txa); > >> + adv748x_csi2_cleanup(&state->txb); > >> + > >> + adv748x_unregister_clients(state); > >> + adv748x_dt_cleanup(state); > >> + mutex_destroy(&state->mutex); > >> + > >> + kfree(state); > >> + > >> + return 0; > >> +} > >> + > >> +static const struct i2c_device_id adv748x_id[] = { > >> + { "adv7481", 0 }, > >> + { "adv7482", 0 }, > >> + { }, > >> +}; > >> + > >> +static const struct of_device_id adv748x_of_table[] = { > >> + { .compatible = "adi,adv7481", }, > >> + { .compatible = "adi,adv7482", }, > >> + { } > >> +}; > >> +MODULE_DEVICE_TABLE(of, adv748x_of_table); > >> + > >> +static struct i2c_driver adv748x_driver = { > >> + .driver = { > >> + .name = "adv748x", > >> + .of_match_table = adv748x_of_table, > >> + }, > >> + .probe = adv748x_probe, > >> + .remove = adv748x_remove, > >> + .id_table = adv748x_id, > >> +}; > >> + > >> +module_i2c_driver(adv748x_driver); > >> + > >> +MODULE_AUTHOR("Kieran Bingham <kieran.bingham@xxxxxxxxxxxxxxxx>"); > >> +MODULE_DESCRIPTION("ADV748X video decoder"); > >> +MODULE_LICENSE("GPL v2"); > >> diff --git a/drivers/media/i2c/adv748x/adv748x-csi2.c b/drivers/media/i2c/adv748x/adv748x-csi2.c > >> new file mode 100644 > >> index 000000000000..b4fee7f52d6a > >> --- /dev/null > >> +++ b/drivers/media/i2c/adv748x/adv748x-csi2.c > >> @@ -0,0 +1,327 @@ > >> +/* > >> + * Driver for Analog Devices ADV748X CSI-2 Transmitter > >> + * > >> + * Copyright (C) 2017 Renesas Electronics Corp. > >> + * > >> + * This program is free software; you can redistribute it and/or modify it > >> + * under the terms of the GNU General Public License as published by the > >> + * Free Software Foundation; either version 2 of the License, or (at your > >> + * option) any later version. > >> + */ > >> + > >> +#include <linux/module.h> > >> +#include <linux/mutex.h> > >> + > >> +#include <media/v4l2-ctrls.h> > >> +#include <media/v4l2-device.h> > >> +#include <media/v4l2-ioctl.h> > >> + > >> +#include "adv748x.h" > >> + > >> +static bool is_txa(struct adv748x_csi2 *tx) > >> +{ > >> + return tx == &tx->state->txa; > >> +} > >> + > >> +static int adv748x_csi2_set_virtual_channel(struct adv748x_csi2 *tx, > >> + unsigned int vc) > >> +{ > >> + return tx_write(tx, ADV748X_CSI_VC_REF, vc << ADV748X_CSI_VC_REF_SHIFT); > >> +} > >> + > >> +/** > >> + * adv748x_csi2_register_link : Register and link internal entities > >> + * > >> + * @tx: CSI2 private entity > >> + * @v4l2_dev: Video registration device > >> + * @src: Source subdevice to establish link > >> + * @src_pad: Pad number of source to link to this @tx > >> + * > >> + * Ensure that the subdevice is registered against the v4l2_device, and link the > >> + * source pad to the sink pad of the CSI2 bus entity. > >> + */ > >> +static int adv748x_csi2_register_link(struct adv748x_csi2 *tx, > >> + struct v4l2_device *v4l2_dev, > >> + struct v4l2_subdev *src, > >> + unsigned int src_pad) > >> +{ > >> + int enabled = MEDIA_LNK_FL_ENABLED; > >> + int ret; > >> + > >> + /* > >> + * Dynamic linking of the AFE is not supported. > >> + * Register the links as immutable. > >> + */ > >> + enabled |= MEDIA_LNK_FL_IMMUTABLE; > >> + > >> + if (!src->v4l2_dev) { > >> + ret = v4l2_device_register_subdev(v4l2_dev, src); > >> + if (ret) > >> + return ret; > >> + } > >> + > >> + return media_create_pad_link(&src->entity, src_pad, > >> + &tx->sd.entity, ADV748X_CSI2_SINK, > >> + enabled); > >> +} > >> + > >> +/* ----------------------------------------------------------------------------- > >> + * v4l2_subdev_internal_ops > >> + * > >> + * We use the internal registered operation to be able to ensure that our > >> + * incremental subdevices (not connected in the forward path) can be registered > >> + * against the resulting video path and media device. > >> + */ > >> + > >> +static int adv748x_csi2_registered(struct v4l2_subdev *sd) > >> +{ > >> + struct adv748x_csi2 *tx = adv748x_sd_to_csi2(sd); > >> + struct adv748x_state *state = tx->state; > >> + > >> + adv_dbg(state, "Registered %s (%s)", is_txa(tx) ? "TXA":"TXB", > >> + sd->name); > >> + > >> + /* > >> + * The adv748x hardware allows the AFE to route through the TXA, however > >> + * this is not currently supported in this driver. > >> + * > >> + * Link HDMI->TXA, and AFE->TXB directly. > >> + */ > >> + if (is_txa(tx)) { > >> + return adv748x_csi2_register_link(tx, sd->v4l2_dev, > >> + &state->hdmi.sd, > >> + ADV748X_HDMI_SOURCE); > >> + } else { > >> + return adv748x_csi2_register_link(tx, sd->v4l2_dev, > >> + &state->afe.sd, > >> + ADV748X_AFE_SOURCE); > >> + } > >> +} > >> + > >> +static const struct v4l2_subdev_internal_ops adv748x_csi2_internal_ops = { > >> + .registered = adv748x_csi2_registered, > >> +}; > >> + > >> +/* ----------------------------------------------------------------------------- > >> + * v4l2_subdev_video_ops > >> + */ > >> + > >> +static int adv748x_csi2_s_stream(struct v4l2_subdev *sd, int enable) > >> +{ > >> + struct adv748x_csi2 *tx = adv748x_sd_to_csi2(sd); > >> + struct v4l2_subdev *src; > >> + > >> + src = adv748x_get_remote_sd(&tx->pads[ADV748X_CSI2_SINK]); > >> + if (!src) > >> + return -EPIPE; > >> + > >> + return v4l2_subdev_call(src, video, s_stream, enable); > >> +} > >> + > >> +static const struct v4l2_subdev_video_ops adv748x_csi2_video_ops = { > >> + .s_stream = adv748x_csi2_s_stream, > >> +}; > >> + > >> +/* ----------------------------------------------------------------------------- > >> + * v4l2_subdev_pad_ops > >> + * > >> + * The CSI2 bus pads are ignorant to the data sizes or formats. > >> + * But we must support setting the pad formats for format propagation. > >> + */ > >> + > >> +static struct v4l2_mbus_framefmt * > >> +adv748x_csi2_get_pad_format(struct v4l2_subdev *sd, > >> + struct v4l2_subdev_pad_config *cfg, > >> + unsigned int pad, u32 which) > >> +{ > >> + struct adv748x_csi2 *tx = adv748x_sd_to_csi2(sd); > >> + > >> + if (which == V4L2_SUBDEV_FORMAT_TRY) > >> + return v4l2_subdev_get_try_format(sd, cfg, pad); > >> + > >> + return &tx->format; > >> +} > >> + > >> +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); > >> + > >> + 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); > >> + > >> + if (sdformat->pad == ADV748X_CSI2_SOURCE) { > >> + const struct v4l2_mbus_framefmt *sink_fmt; > >> + > >> + sink_fmt = adv748x_csi2_get_pad_format(sd, cfg, > >> + ADV748X_CSI2_SINK, > >> + sdformat->which); > >> + > >> + if (!sink_fmt) { > >> + ret = -EINVAL; > >> + goto unlock; > >> + } > >> + > >> + sdformat->format = *sink_fmt; > >> + } > >> + > >> + *mbusformat = sdformat->format; > >> + > >> +unlock: > >> + mutex_unlock(&state->mutex); > >> + > >> + return ret; > >> +} > >> + > >> +static const struct v4l2_subdev_pad_ops adv748x_csi2_pad_ops = { > >> + .get_fmt = adv748x_csi2_get_format, > >> + .set_fmt = adv748x_csi2_set_format, > >> +}; > >> + > >> +/* ----------------------------------------------------------------------------- > >> + * v4l2_subdev_ops > >> + */ > >> + > >> +static const struct v4l2_subdev_ops adv748x_csi2_ops = { > >> + .video = &adv748x_csi2_video_ops, > >> + .pad = &adv748x_csi2_pad_ops, > >> +}; > >> + > >> +/* ----------------------------------------------------------------------------- > >> + * Subdev module and controls > >> + */ > >> + > >> +int adv748x_csi2_set_pixelrate(struct v4l2_subdev *sd, s64 rate) > >> +{ > >> + struct v4l2_ctrl *ctrl; > >> + > >> + ctrl = v4l2_ctrl_find(sd->ctrl_handler, V4L2_CID_PIXEL_RATE); > >> + if (!ctrl) > >> + return -EINVAL; > >> + > >> + return v4l2_ctrl_s_ctrl_int64(ctrl, rate); > >> +} > >> + > >> +static int adv748x_csi2_s_ctrl(struct v4l2_ctrl *ctrl) > >> +{ > >> + switch (ctrl->id) { > >> + case V4L2_CID_PIXEL_RATE: > >> + return 0; > >> + default: > >> + return -EINVAL; > >> + } > >> +} > >> + > >> +static const struct v4l2_ctrl_ops adv748x_csi2_ctrl_ops = { > >> + .s_ctrl = adv748x_csi2_s_ctrl, > >> +}; > >> + > >> +static int adv748x_csi2_init_controls(struct adv748x_csi2 *tx) > >> +{ > >> + struct v4l2_ctrl *ctrl; > >> + > >> + v4l2_ctrl_handler_init(&tx->ctrl_hdl, 1); > >> + > >> + ctrl = v4l2_ctrl_new_std(&tx->ctrl_hdl, &adv748x_csi2_ctrl_ops, > >> + V4L2_CID_PIXEL_RATE, 1, INT_MAX, 1, 1); > >> + > >> + tx->sd.ctrl_handler = &tx->ctrl_hdl; > >> + if (tx->ctrl_hdl.error) { > >> + v4l2_ctrl_handler_free(&tx->ctrl_hdl); > >> + return tx->ctrl_hdl.error; > >> + } > >> + > >> + return v4l2_ctrl_handler_setup(&tx->ctrl_hdl); > >> +} > >> + > >> +int adv748x_csi2_init(struct adv748x_state *state, struct adv748x_csi2 *tx) > >> +{ > >> + struct device_node *ep; > >> + int ret; > >> + > >> + /* We can not use container_of to get back to the state with two TXs */ > >> + tx->state = state; > >> + tx->page = is_txa(tx) ? ADV748X_PAGE_TXA : ADV748X_PAGE_TXB; > >> + > >> + ep = state->endpoints[is_txa(tx) ? ADV748X_PORT_TXA : ADV748X_PORT_TXB]; > >> + if (!ep) { > >> + adv_err(state, "No endpoint found for %s\n", > >> + is_txa(tx) ? "txa" : "txb"); > >> + return -ENODEV; > >> + } > >> + > >> + /* Initialise the virtual channel */ > >> + adv748x_csi2_set_virtual_channel(tx, 0); > >> + > >> + adv748x_subdev_init(&tx->sd, state, &adv748x_csi2_ops, > >> + MEDIA_ENT_F_UNKNOWN, > >> + is_txa(tx) ? "txa" : "txb"); > >> + > >> + /* Ensure that matching is based upon the endpoint fwnodes */ > >> + tx->sd.fwnode = of_fwnode_handle(ep); > >> + > >> + /* Register internal ops for incremental subdev registration */ > >> + tx->sd.internal_ops = &adv748x_csi2_internal_ops; > >> + > >> + tx->pads[ADV748X_CSI2_SINK].flags = MEDIA_PAD_FL_SINK; > >> + tx->pads[ADV748X_CSI2_SOURCE].flags = MEDIA_PAD_FL_SOURCE; > >> + > >> + ret = media_entity_pads_init(&tx->sd.entity, ADV748X_CSI2_NR_PADS, > >> + tx->pads); > >> + if (ret) > >> + return ret; > >> + > >> + ret = adv748x_csi2_init_controls(tx); > >> + if (ret) > >> + goto err_free_media; > >> + > >> + ret = v4l2_async_register_subdev(&tx->sd); > >> + if (ret) > >> + goto err_free_ctrl; > >> + > >> + return 0; > >> + > >> +err_free_ctrl: > >> + v4l2_ctrl_handler_free(&tx->ctrl_hdl); > >> +err_free_media: > >> + media_entity_cleanup(&tx->sd.entity); > >> + > >> + return ret; > >> +} > >> + > >> +void adv748x_csi2_cleanup(struct adv748x_csi2 *tx) > >> +{ > >> + v4l2_async_unregister_subdev(&tx->sd); > >> + media_entity_cleanup(&tx->sd.entity); > >> + v4l2_ctrl_handler_free(&tx->ctrl_hdl); > >> +} > >> diff --git a/drivers/media/i2c/adv748x/adv748x-hdmi.c b/drivers/media/i2c/adv748x/adv748x-hdmi.c > >> new file mode 100644 > >> index 000000000000..4da4253553fc > >> --- /dev/null > >> +++ b/drivers/media/i2c/adv748x/adv748x-hdmi.c > >> @@ -0,0 +1,768 @@ > >> +/* > >> + * Driver for Analog Devices ADV748X HDMI receiver and Component Processor (CP) > >> + * > >> + * Copyright (C) 2017 Renesas Electronics Corp. > >> + * > >> + * This program is free software; you can redistribute it and/or modify it > >> + * under the terms of the GNU General Public License as published by the > >> + * Free Software Foundation; either version 2 of the License, or (at your > >> + * option) any later version. > >> + */ > >> + > >> +#include <linux/module.h> > >> +#include <linux/mutex.h> > >> + > >> +#include <media/v4l2-ctrls.h> > >> +#include <media/v4l2-device.h> > >> +#include <media/v4l2-dv-timings.h> > >> +#include <media/v4l2-ioctl.h> > >> + > >> +#include <uapi/linux/v4l2-dv-timings.h> > >> + > >> +#include "adv748x.h" > >> + > >> +/* ----------------------------------------------------------------------------- > >> + * HDMI and CP > >> + */ > >> + > >> +#define ADV748X_HDMI_MIN_WIDTH 640 > >> +#define ADV748X_HDMI_MAX_WIDTH 1920 > >> +#define ADV748X_HDMI_MIN_HEIGHT 480 > >> +#define ADV748X_HDMI_MAX_HEIGHT 1200 > >> + > >> +/* V4L2_DV_BT_CEA_720X480I59_94 - 0.5 MHz */ > >> +#define ADV748X_HDMI_MIN_PIXELCLOCK 13000000 > >> +/* V4L2_DV_BT_DMT_1600X1200P60 */ > >> +#define ADV748X_HDMI_MAX_PIXELCLOCK 162000000 > >> + > >> +static const struct v4l2_dv_timings_cap adv748x_hdmi_timings_cap = { > >> + .type = V4L2_DV_BT_656_1120, > >> + /* keep this initialization for compatibility with GCC < 4.4.6 */ > >> + .reserved = { 0 }, > >> + > >> + V4L2_INIT_BT_TIMINGS(ADV748X_HDMI_MIN_WIDTH, ADV748X_HDMI_MAX_WIDTH, > >> + ADV748X_HDMI_MIN_HEIGHT, ADV748X_HDMI_MAX_HEIGHT, > >> + ADV748X_HDMI_MIN_PIXELCLOCK, > >> + ADV748X_HDMI_MAX_PIXELCLOCK, > >> + V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT, > >> + V4L2_DV_BT_CAP_PROGRESSIVE) > >> +}; > >> + > >> +struct adv748x_hdmi_video_standards { > >> + struct v4l2_dv_timings timings; > >> + u8 vid_std; > >> + u8 v_freq; > >> +}; > >> + > >> +static const struct adv748x_hdmi_video_standards > >> +adv748x_hdmi_video_standards[] = { > >> + { V4L2_DV_BT_CEA_720X480P59_94, 0x4a, 0x00 }, > >> + { V4L2_DV_BT_CEA_720X576P50, 0x4b, 0x00 }, > >> + { V4L2_DV_BT_CEA_1280X720P60, 0x53, 0x00 }, > >> + { V4L2_DV_BT_CEA_1280X720P50, 0x53, 0x01 }, > >> + { V4L2_DV_BT_CEA_1280X720P30, 0x53, 0x02 }, > >> + { V4L2_DV_BT_CEA_1280X720P25, 0x53, 0x03 }, > >> + { V4L2_DV_BT_CEA_1280X720P24, 0x53, 0x04 }, > >> + { V4L2_DV_BT_CEA_1920X1080P60, 0x5e, 0x00 }, > >> + { V4L2_DV_BT_CEA_1920X1080P50, 0x5e, 0x01 }, > >> + { V4L2_DV_BT_CEA_1920X1080P30, 0x5e, 0x02 }, > >> + { V4L2_DV_BT_CEA_1920X1080P25, 0x5e, 0x03 }, > >> + { V4L2_DV_BT_CEA_1920X1080P24, 0x5e, 0x04 }, > >> + /* SVGA */ > >> + { V4L2_DV_BT_DMT_800X600P56, 0x80, 0x00 }, > >> + { V4L2_DV_BT_DMT_800X600P60, 0x81, 0x00 }, > >> + { V4L2_DV_BT_DMT_800X600P72, 0x82, 0x00 }, > >> + { V4L2_DV_BT_DMT_800X600P75, 0x83, 0x00 }, > >> + { V4L2_DV_BT_DMT_800X600P85, 0x84, 0x00 }, > >> + /* SXGA */ > >> + { V4L2_DV_BT_DMT_1280X1024P60, 0x85, 0x00 }, > >> + { V4L2_DV_BT_DMT_1280X1024P75, 0x86, 0x00 }, > >> + /* VGA */ > >> + { V4L2_DV_BT_DMT_640X480P60, 0x88, 0x00 }, > >> + { V4L2_DV_BT_DMT_640X480P72, 0x89, 0x00 }, > >> + { V4L2_DV_BT_DMT_640X480P75, 0x8a, 0x00 }, > >> + { V4L2_DV_BT_DMT_640X480P85, 0x8b, 0x00 }, > >> + /* XGA */ > >> + { V4L2_DV_BT_DMT_1024X768P60, 0x8c, 0x00 }, > >> + { V4L2_DV_BT_DMT_1024X768P70, 0x8d, 0x00 }, > >> + { V4L2_DV_BT_DMT_1024X768P75, 0x8e, 0x00 }, > >> + { V4L2_DV_BT_DMT_1024X768P85, 0x8f, 0x00 }, > >> + /* UXGA */ > >> + { V4L2_DV_BT_DMT_1600X1200P60, 0x96, 0x00 }, > >> +}; > >> + > >> +static void adv748x_hdmi_fill_format(struct adv748x_hdmi *hdmi, > >> + struct v4l2_mbus_framefmt *fmt) > >> +{ > >> + memset(fmt, 0, sizeof(*fmt)); > >> + > >> + fmt->code = MEDIA_BUS_FMT_RGB888_1X24; > >> + fmt->field = hdmi->timings.bt.interlaced ? > >> + V4L2_FIELD_ALTERNATE : V4L2_FIELD_NONE; > >> + > >> + /* TODO: The colorspace depends on the AVI InfoFrame contents */ > >> + fmt->colorspace = V4L2_COLORSPACE_SRGB; > >> + > >> + fmt->width = hdmi->timings.bt.width; > >> + fmt->height = hdmi->timings.bt.height; > >> +} > >> + > >> +static void adv748x_fill_optional_dv_timings(struct v4l2_dv_timings *timings) > >> +{ > >> + v4l2_find_dv_timings_cap(timings, &adv748x_hdmi_timings_cap, > >> + 250000, NULL, NULL); > >> +} > >> + > >> +static bool adv748x_hdmi_has_signal(struct adv748x_state *state) > >> +{ > >> + int val; > >> + > >> + /* Check that VERT_FILTER and DE_REGEN is locked */ > >> + val = hdmi_read(state, ADV748X_HDMI_LW1); > >> + return (val & ADV748X_HDMI_LW1_VERT_FILTER) && > >> + (val & ADV748X_HDMI_LW1_DE_REGEN); > >> +} > >> + > >> +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 > >> + * 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; > >> +} > >> + > >> +/* > >> + * adv748x_hdmi_set_de_timings: Adjust horizontal picture offset through DE > >> + * > >> + * HDMI CP uses a Data Enable synchronisation timing reference > >> + * > >> + * Vary the leading and trailing edge position of the DE signal output by the CP > >> + * core. Values are stored as signed-twos-complement in one-pixel-clock units > >> + * > >> + * The start and end are shifted equally by the 10-bit shift value. > >> + */ > >> +static void adv748x_hdmi_set_de_timings(struct adv748x_state *state, int shift) > >> +{ > >> + u8 high, low; > >> + > >> + /* POS_HIGH stores bits 8 and 9 of both the start and end */ > >> + high = ADV748X_CP_DE_POS_HIGH_SET; > >> + high |= (shift & 0x300) >> 8; > >> + low = shift & 0xff; > >> + > >> + /* The sequence of the writes is important and must be followed */ > >> + cp_write(state, ADV748X_CP_DE_POS_HIGH, high); > >> + cp_write(state, ADV748X_CP_DE_POS_END_LOW, low); > >> + > >> + high |= (shift & 0x300) >> 6; > >> + > >> + cp_write(state, ADV748X_CP_DE_POS_HIGH, high); > >> + cp_write(state, ADV748X_CP_DE_POS_START_LOW, low); > >> +} > >> + > >> +static int adv748x_hdmi_set_video_timings(struct adv748x_state *state, > >> + const struct v4l2_dv_timings *timings) > >> +{ > >> + const struct adv748x_hdmi_video_standards *stds = > >> + adv748x_hdmi_video_standards; > >> + unsigned int i; > >> + > >> + for (i = 0; i < ARRAY_SIZE(adv748x_hdmi_video_standards); i++) { > >> + if (!v4l2_match_dv_timings(timings, &stds[i].timings, 250000, > >> + false)) > >> + continue; > >> + } > >> + > >> + if (i >= ARRAY_SIZE(adv748x_hdmi_video_standards)) > >> + return -EINVAL; > >> + > >> + /* > >> + * When setting cp_vid_std to either 720p, 1080i, or 1080p, the video > >> + * will get shifted horizontally to the left in active video mode. > >> + * The de_h_start and de_h_end controls are used to centre the picture > >> + * correctly > >> + */ > >> + switch (stds[i].vid_std) { > >> + case 0x53: /* 720p */ > >> + adv748x_hdmi_set_de_timings(state, -40); > >> + break; > >> + case 0x54: /* 1080i */ > >> + case 0x5e: /* 1080p */ > >> + adv748x_hdmi_set_de_timings(state, -44); > >> + break; > >> + default: > >> + adv748x_hdmi_set_de_timings(state, 0); > >> + break; > >> + } > >> + > >> + io_write(state, ADV748X_IO_VID_STD, stds[i].vid_std); > >> + io_clrset(state, ADV748X_IO_DATAPATH, ADV748X_IO_DATAPATH_VFREQ_M, > >> + stds[i].v_freq << ADV748X_IO_DATAPATH_VFREQ_SHIFT); > >> + > >> + return 0; > >> +} > >> + > >> +/* ----------------------------------------------------------------------------- > >> + * v4l2_subdev_video_ops > >> + */ > >> + > >> +static int adv748x_hdmi_s_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); > >> + int ret; > >> + > >> + if (!timings) > >> + return -EINVAL; > >> + > >> + if (v4l2_match_dv_timings(&hdmi->timings, timings, 0, false)) > >> + return 0; > >> + > >> + if (!v4l2_valid_dv_timings(timings, &adv748x_hdmi_timings_cap, > >> + NULL, NULL)) > >> + return -ERANGE; > >> + > >> + adv748x_fill_optional_dv_timings(timings); > >> + > >> + mutex_lock(&state->mutex); > >> + > >> + ret = adv748x_hdmi_set_video_timings(state, timings); > >> + if (ret) > >> + goto error; > >> + > >> + hdmi->timings = *timings; > >> + > >> + cp_clrset(state, ADV748X_CP_VID_ADJ_2, ADV748X_CP_VID_ADJ_2_INTERLACED, > >> + timings->bt.interlaced ? > >> + ADV748X_CP_VID_ADJ_2_INTERLACED : 0); > >> + > >> + mutex_unlock(&state->mutex); > >> + > >> + return 0; > >> + > >> +error: > >> + mutex_unlock(&state->mutex); > >> + return ret; > >> +} > >> + > >> +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); > >> + > >> + *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; > >> + > >> + 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); > >> + > >> + *status = adv748x_hdmi_has_signal(state) ? 0 : V4L2_IN_ST_NO_SIGNAL; > >> + > >> + mutex_unlock(&state->mutex); > >> + > >> + return 0; > >> +} > >> + > >> +static int adv748x_hdmi_s_stream(struct v4l2_subdev *sd, int enable) > >> +{ > >> + struct adv748x_hdmi *hdmi = adv748x_sd_to_hdmi(sd); > >> + struct adv748x_state *state = adv748x_hdmi_to_state(hdmi); > >> + int ret; > >> + > >> + mutex_lock(&state->mutex); > >> + > >> + ret = adv748x_txa_power(state, enable); > >> + if (ret) > >> + goto done; > >> + > >> + if (adv748x_hdmi_has_signal(state)) > >> + adv_dbg(state, "Detected HDMI signal\n"); > >> + else > >> + adv_dbg(state, "Couldn't detect HDMI video signal\n"); > >> + > >> +done: > >> + mutex_unlock(&state->mutex); > >> + return ret; > >> +} > >> + > >> +static int adv748x_hdmi_g_pixelaspect(struct v4l2_subdev *sd, > >> + struct v4l2_fract *aspect) > >> +{ > >> + aspect->numerator = 1; > >> + aspect->denominator = 1; > >> + > >> + return 0; > >> +} > >> + > >> +static const struct v4l2_subdev_video_ops adv748x_video_ops_hdmi = { > >> + .s_dv_timings = adv748x_hdmi_s_dv_timings, > >> + .g_dv_timings = adv748x_hdmi_g_dv_timings, > >> + .query_dv_timings = adv748x_hdmi_query_dv_timings, > >> + .g_input_status = adv748x_hdmi_g_input_status, > >> + .s_stream = adv748x_hdmi_s_stream, > >> + .g_pixelaspect = adv748x_hdmi_g_pixelaspect, > >> +}; > >> + > >> +/* ----------------------------------------------------------------------------- > >> + * v4l2_subdev_pad_ops > >> + */ > >> + > >> +static int adv748x_hdmi_propagate_pixelrate(struct adv748x_hdmi *hdmi) > >> +{ > >> + struct v4l2_subdev *tx; > >> + struct v4l2_dv_timings timings; > >> + struct v4l2_bt_timings *bt = &timings.bt; > >> + unsigned int fps; > >> + > >> + tx = adv748x_get_remote_sd(&hdmi->pads[ADV748X_HDMI_SOURCE]); > >> + if (!tx) > >> + return -ENOLINK; > >> + > >> + adv748x_hdmi_query_dv_timings(&hdmi->sd, &timings); > >> + > >> + fps = DIV_ROUND_CLOSEST_ULL(bt->pixelclock, > >> + V4L2_DV_BT_FRAME_WIDTH(bt) * > >> + V4L2_DV_BT_FRAME_HEIGHT(bt)); > >> + > >> + return adv748x_csi2_set_pixelrate(tx, bt->width * bt->height * fps); > >> +} > >> + > >> +static int adv748x_hdmi_enum_mbus_code(struct v4l2_subdev *sd, > >> + struct v4l2_subdev_pad_config *cfg, > >> + struct v4l2_subdev_mbus_code_enum *code) > >> +{ > >> + if (code->index != 0) > >> + return -EINVAL; > >> + > >> + code->code = MEDIA_BUS_FMT_RGB888_1X24; > >> + > >> + return 0; > >> +} > >> + > >> +static int adv748x_hdmi_get_format(struct v4l2_subdev *sd, > >> + struct v4l2_subdev_pad_config *cfg, > >> + struct v4l2_subdev_format *sdformat) > >> +{ > >> + struct adv748x_hdmi *hdmi = adv748x_sd_to_hdmi(sd); > >> + struct v4l2_mbus_framefmt *mbusformat; > >> + > >> + if (sdformat->pad != ADV748X_HDMI_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_hdmi_fill_format(hdmi, &sdformat->format); > >> + adv748x_hdmi_propagate_pixelrate(hdmi); > >> + } > >> + > >> + return 0; > >> +} > >> + > >> +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; > >> + > >> + return 0; > >> +} > >> + > >> +static int adv748x_hdmi_get_edid(struct v4l2_subdev *sd, struct v4l2_edid *edid) > >> +{ > >> + struct adv748x_hdmi *hdmi = adv748x_sd_to_hdmi(sd); > >> + > >> + memset(edid->reserved, 0, sizeof(edid->reserved)); > >> + > >> + if (!hdmi->edid.present) > >> + return -ENODATA; > >> + > >> + if (edid->start_block == 0 && edid->blocks == 0) { > >> + edid->blocks = hdmi->edid.blocks; > >> + return 0; > >> + } > >> + > >> + if (edid->start_block >= hdmi->edid.blocks) > >> + return -EINVAL; > >> + > >> + if (edid->start_block + edid->blocks > hdmi->edid.blocks) > >> + edid->blocks = hdmi->edid.blocks - edid->start_block; > >> + > >> + memcpy(edid->edid, hdmi->edid.edid + edid->start_block * 128, > >> + edid->blocks * 128); > >> + > >> + return 0; > >> +} > >> + > >> +static inline int adv748x_hdmi_edid_write_block(struct adv748x_hdmi *hdmi, > >> + unsigned int total_len, const u8 *val) > >> +{ > >> + struct adv748x_state *state = adv748x_hdmi_to_state(hdmi); > >> + int err = 0; > >> + int i = 0; > >> + int len = 0; > >> + > >> + adv_dbg(state, "%s: write EDID block (%d byte)\n", > >> + __func__, total_len); > >> + > >> + while (!err && i < total_len) { > >> + len = (total_len - i) > I2C_SMBUS_BLOCK_MAX ? > >> + I2C_SMBUS_BLOCK_MAX : > >> + (total_len - i); > >> + > >> + err = adv748x_write_block(state, ADV748X_PAGE_EDID, > >> + i, val + i, len); > >> + i += len; > >> + } > >> + > >> + return err; > >> +} > >> + > >> +static int adv748x_hdmi_set_edid(struct v4l2_subdev *sd, struct v4l2_edid *edid) > >> +{ > >> + struct adv748x_hdmi *hdmi = adv748x_sd_to_hdmi(sd); > >> + struct adv748x_state *state = adv748x_hdmi_to_state(hdmi); > >> + int err; > >> + > >> + memset(edid->reserved, 0, sizeof(edid->reserved)); > >> + > >> + if (edid->start_block != 0) > >> + return -EINVAL; > >> + > >> + if (edid->blocks == 0) { > >> + hdmi->edid.blocks = 0; > >> + hdmi->edid.present = 0; > >> + > >> + /* Fall back to a 16:9 aspect ratio */ > >> + hdmi->aspect_ratio.numerator = 16; > >> + hdmi->aspect_ratio.denominator = 9; > >> + > >> + /* Disable the EDID */ > >> + repeater_write(state, ADV748X_REPEATER_EDID_SZ, > >> + edid->blocks << ADV748X_REPEATER_EDID_SZ_SHIFT); > >> + > >> + repeater_write(state, ADV748X_REPEATER_EDID_CTL, 0); > >> + > >> + return 0; > >> + } > >> + > >> + if (edid->blocks > 4) { > >> + edid->blocks = 4; > >> + return -E2BIG; > >> + } > >> + > >> + memcpy(hdmi->edid.edid, edid->edid, 128 * edid->blocks); > >> + hdmi->edid.blocks = edid->blocks; > >> + hdmi->edid.present = true; > >> + > >> + hdmi->aspect_ratio = v4l2_calc_aspect_ratio(edid->edid[0x15], > >> + edid->edid[0x16]); > >> + > >> + err = adv748x_hdmi_edid_write_block(hdmi, 128 * edid->blocks, > >> + hdmi->edid.edid); > >> + if (err < 0) { > >> + v4l2_err(sd, "error %d writing edid pad %d\n", err, edid->pad); > >> + return err; > >> + } > >> + > >> + repeater_write(state, ADV748X_REPEATER_EDID_SZ, > >> + edid->blocks << ADV748X_REPEATER_EDID_SZ_SHIFT); > >> + > >> + repeater_write(state, ADV748X_REPEATER_EDID_CTL, > >> + ADV748X_REPEATER_EDID_CTL_EN); > >> + > >> + return 0; > >> +} > >> + > >> +static bool adv748x_hdmi_check_dv_timings(const struct v4l2_dv_timings *timings, > >> + void *hdl) > >> +{ > >> + const struct adv748x_hdmi_video_standards *stds = > >> + adv748x_hdmi_video_standards; > >> + unsigned int i; > >> + > >> + for (i = 0; stds[i].timings.bt.width; i++) > >> + if (v4l2_match_dv_timings(timings, &stds[i].timings, 0, false)) > >> + return true; > >> + > >> + return false; > >> +} > >> + > >> +static int adv748x_hdmi_enum_dv_timings(struct v4l2_subdev *sd, > >> + struct v4l2_enum_dv_timings *timings) > >> +{ > >> + return v4l2_enum_dv_timings_cap(timings, &adv748x_hdmi_timings_cap, > >> + adv748x_hdmi_check_dv_timings, NULL); > >> +} > >> + > >> +static int adv748x_hdmi_dv_timings_cap(struct v4l2_subdev *sd, > >> + struct v4l2_dv_timings_cap *cap) > >> +{ > >> + *cap = adv748x_hdmi_timings_cap; > >> + return 0; > >> +} > >> + > >> +static const struct v4l2_subdev_pad_ops adv748x_pad_ops_hdmi = { > >> + .enum_mbus_code = adv748x_hdmi_enum_mbus_code, > >> + .set_fmt = adv748x_hdmi_set_format, > >> + .get_fmt = adv748x_hdmi_get_format, > >> + .get_edid = adv748x_hdmi_get_edid, > >> + .set_edid = adv748x_hdmi_set_edid, > >> + .dv_timings_cap = adv748x_hdmi_dv_timings_cap, > >> + .enum_dv_timings = adv748x_hdmi_enum_dv_timings, > >> +}; > >> + > >> +/* ----------------------------------------------------------------------------- > >> + * v4l2_subdev_ops > >> + */ > >> + > >> +static const struct v4l2_subdev_ops adv748x_ops_hdmi = { > >> + .video = &adv748x_video_ops_hdmi, > >> + .pad = &adv748x_pad_ops_hdmi, > >> +}; > >> + > >> +/* ----------------------------------------------------------------------------- > >> + * Controls > >> + */ > >> + > >> +static const char * const hdmi_ctrl_patgen_menu[] = { > >> + "Disabled", > >> + "Solid Color", > >> + "Color Bars", > >> + "Ramp Grey", > >> + "Ramp Blue", > >> + "Ramp Red", > >> + "Checkered" > >> +}; > >> + > >> +static int adv748x_hdmi_s_ctrl(struct v4l2_ctrl *ctrl) > >> +{ > >> + struct adv748x_hdmi *hdmi = adv748x_ctrl_to_hdmi(ctrl); > >> + struct adv748x_state *state = adv748x_hdmi_to_state(hdmi); > >> + int ret; > >> + u8 pattern; > >> + > >> + /* Enable video adjustment first */ > >> + ret = cp_clrset(state, ADV748X_CP_VID_ADJ, > >> + ADV748X_CP_VID_ADJ_ENABLE, > >> + ADV748X_CP_VID_ADJ_ENABLE); > >> + if (ret < 0) > >> + return ret; > >> + > >> + switch (ctrl->id) { > >> + case V4L2_CID_BRIGHTNESS: > >> + ret = cp_write(state, ADV748X_CP_BRI, ctrl->val); > >> + break; > >> + case V4L2_CID_HUE: > >> + ret = cp_write(state, ADV748X_CP_HUE, ctrl->val); > >> + break; > >> + case V4L2_CID_CONTRAST: > >> + ret = cp_write(state, ADV748X_CP_CON, ctrl->val); > >> + break; > >> + case V4L2_CID_SATURATION: > >> + ret = cp_write(state, ADV748X_CP_SAT, ctrl->val); > >> + break; > >> + case V4L2_CID_TEST_PATTERN: > >> + pattern = ctrl->val; > >> + > >> + /* Pattern is 0-indexed. Ctrl Menu is 1-indexed */ > >> + if (pattern) { > >> + pattern--; > >> + pattern |= ADV748X_CP_PAT_GEN_EN; > >> + } > >> + > >> + ret = cp_write(state, ADV748X_CP_PAT_GEN, pattern); > >> + > >> + break; > >> + default: > >> + return -EINVAL; > >> + } > >> + > >> + return ret; > >> +} > >> + > >> +static const struct v4l2_ctrl_ops adv748x_hdmi_ctrl_ops = { > >> + .s_ctrl = adv748x_hdmi_s_ctrl, > >> +}; > >> + > >> +static int adv748x_hdmi_init_controls(struct adv748x_hdmi *hdmi) > >> +{ > >> + struct adv748x_state *state = adv748x_hdmi_to_state(hdmi); > >> + > >> + v4l2_ctrl_handler_init(&hdmi->ctrl_hdl, 5); > >> + > >> + /* Use our mutex for the controls */ > >> + hdmi->ctrl_hdl.lock = &state->mutex; > >> + > >> + v4l2_ctrl_new_std(&hdmi->ctrl_hdl, &adv748x_hdmi_ctrl_ops, > >> + V4L2_CID_BRIGHTNESS, ADV748X_CP_BRI_MIN, > >> + ADV748X_CP_BRI_MAX, 1, ADV748X_CP_BRI_DEF); > >> + v4l2_ctrl_new_std(&hdmi->ctrl_hdl, &adv748x_hdmi_ctrl_ops, > >> + V4L2_CID_CONTRAST, ADV748X_CP_CON_MIN, > >> + ADV748X_CP_CON_MAX, 1, ADV748X_CP_CON_DEF); > >> + v4l2_ctrl_new_std(&hdmi->ctrl_hdl, &adv748x_hdmi_ctrl_ops, > >> + V4L2_CID_SATURATION, ADV748X_CP_SAT_MIN, > >> + ADV748X_CP_SAT_MAX, 1, ADV748X_CP_SAT_DEF); > >> + v4l2_ctrl_new_std(&hdmi->ctrl_hdl, &adv748x_hdmi_ctrl_ops, > >> + V4L2_CID_HUE, ADV748X_CP_HUE_MIN, > >> + ADV748X_CP_HUE_MAX, 1, ADV748X_CP_HUE_DEF); > >> + > >> + /* > >> + * Todo: V4L2_CID_DV_RX_POWER_PRESENT should also be supported when > >> + * interrupts are handled correctly > >> + */ > >> + > >> + v4l2_ctrl_new_std_menu_items(&hdmi->ctrl_hdl, &adv748x_hdmi_ctrl_ops, > >> + V4L2_CID_TEST_PATTERN, > >> + ARRAY_SIZE(hdmi_ctrl_patgen_menu) - 1, > >> + 0, 0, hdmi_ctrl_patgen_menu); > >> + > >> + hdmi->sd.ctrl_handler = &hdmi->ctrl_hdl; > >> + if (hdmi->ctrl_hdl.error) { > >> + v4l2_ctrl_handler_free(&hdmi->ctrl_hdl); > >> + return hdmi->ctrl_hdl.error; > >> + } > >> + > >> + return v4l2_ctrl_handler_setup(&hdmi->ctrl_hdl); > >> +} > >> + > >> +int adv748x_hdmi_init(struct adv748x_hdmi *hdmi) > >> +{ > >> + struct adv748x_state *state = adv748x_hdmi_to_state(hdmi); > >> + static const struct v4l2_dv_timings cea1280x720 = > >> + V4L2_DV_BT_CEA_1280X720P30; > >> + int ret; > >> + > >> + hdmi->timings = cea1280x720; > >> + > >> + /* Initialise a default 16:9 aspect ratio */ > >> + hdmi->aspect_ratio.numerator = 16; > >> + hdmi->aspect_ratio.denominator = 9; > >> + > >> + adv748x_subdev_init(&hdmi->sd, state, &adv748x_ops_hdmi, > >> + MEDIA_ENT_F_IO_DTV, "hdmi"); > >> + > >> + hdmi->pads[ADV748X_HDMI_SINK].flags = MEDIA_PAD_FL_SINK; > >> + hdmi->pads[ADV748X_HDMI_SOURCE].flags = MEDIA_PAD_FL_SOURCE; > >> + > >> + ret = media_entity_pads_init(&hdmi->sd.entity, > >> + ADV748X_HDMI_NR_PADS, hdmi->pads); > >> + if (ret) > >> + return ret; > >> + > >> + ret = adv748x_hdmi_init_controls(hdmi); > >> + if (ret) > >> + goto err_free_media; > >> + > >> + return 0; > >> + > >> +err_free_media: > >> + media_entity_cleanup(&hdmi->sd.entity); > >> + > >> + return ret; > >> +} > >> + > >> +void adv748x_hdmi_cleanup(struct adv748x_hdmi *hdmi) > >> +{ > >> + v4l2_device_unregister_subdev(&hdmi->sd); > >> + media_entity_cleanup(&hdmi->sd.entity); > >> + v4l2_ctrl_handler_free(&hdmi->ctrl_hdl); > >> +} > >> diff --git a/drivers/media/i2c/adv748x/adv748x.h b/drivers/media/i2c/adv748x/adv748x.h > >> new file mode 100644 > >> index 000000000000..cc4151b5b31e > >> --- /dev/null > >> +++ b/drivers/media/i2c/adv748x/adv748x.h > >> @@ -0,0 +1,425 @@ > >> +/* > >> + * Driver for Analog Devices ADV748X video decoder and HDMI receiver > >> + * > >> + * Copyright (C) 2017 Renesas Electronics Corp. > >> + * > >> + * This program is free software; you can redistribute it and/or modify it > >> + * under the terms of the GNU General Public License as published by the > >> + * Free Software Foundation; either version 2 of the License, or (at your > >> + * option) any later version. > >> + * > >> + * Authors: > >> + * Koji Matsuoka <koji.matsuoka.xm@xxxxxxxxxxx> > >> + * Niklas Söderlund <niklas.soderlund@xxxxxxxxxxxx> > >> + * Kieran Bingham <kieran.bingham@xxxxxxxxxxxxxxxx> > >> + * > >> + * The ADV748x range of receivers have the following configurations: > >> + * > >> + * Analog HDMI MHL 4-Lane 1-Lane > >> + * In In CSI CSI > >> + * ADV7480 X X X > >> + * ADV7481 X X X X X > >> + * ADV7482 X X X X > >> + */ > >> + > >> +#include <linux/i2c.h> > >> + > >> +#ifndef _ADV748X_H_ > >> +#define _ADV748X_H_ > >> + > >> +/* I2C slave addresses */ > >> +#define ADV748X_I2C_IO 0x70 /* IO Map */ > >> +#define ADV748X_I2C_DPLL 0x26 /* DPLL Map */ > >> +#define ADV748X_I2C_CP 0x22 /* CP Map */ > >> +#define ADV748X_I2C_HDMI 0x34 /* HDMI Map */ > >> +#define ADV748X_I2C_EDID 0x36 /* EDID Map */ > >> +#define ADV748X_I2C_REPEATER 0x32 /* HDMI RX Repeater Map */ > >> +#define ADV748X_I2C_INFOFRAME 0x31 /* HDMI RX InfoFrame Map */ > >> +#define ADV748X_I2C_CEC 0x41 /* CEC Map */ > >> +#define ADV748X_I2C_SDP 0x79 /* SDP Map */ > >> +#define ADV748X_I2C_TXB 0x48 /* CSI-TXB Map */ > >> +#define ADV748X_I2C_TXA 0x4a /* CSI-TXA Map */ > >> + > >> +enum adv748x_page { > >> + ADV748X_PAGE_IO, > >> + ADV748X_PAGE_DPLL, > >> + ADV748X_PAGE_CP, > >> + ADV748X_PAGE_HDMI, > >> + ADV748X_PAGE_EDID, > >> + ADV748X_PAGE_REPEATER, > >> + ADV748X_PAGE_INFOFRAME, > >> + ADV748X_PAGE_CEC, > >> + ADV748X_PAGE_SDP, > >> + ADV748X_PAGE_TXB, > >> + ADV748X_PAGE_TXA, > >> + ADV748X_PAGE_MAX, > >> + > >> + /* Fake pages for register sequences */ > >> + ADV748X_PAGE_WAIT, /* Wait x msec */ > >> + ADV748X_PAGE_EOR, /* End Mark */ > >> +}; > >> + > >> +/** > >> + * enum adv748x_ports - Device tree port number definitions > >> + * > >> + * The ADV748X ports define the mapping between subdevices > >> + * and the device tree specification > >> + */ > >> +enum adv748x_ports { > >> + ADV748X_PORT_AIN0 = 0, > >> + ADV748X_PORT_AIN1 = 1, > >> + ADV748X_PORT_AIN2 = 2, > >> + ADV748X_PORT_AIN3 = 3, > >> + ADV748X_PORT_AIN4 = 4, > >> + ADV748X_PORT_AIN5 = 5, > >> + ADV748X_PORT_AIN6 = 6, > >> + ADV748X_PORT_AIN7 = 7, > >> + ADV748X_PORT_HDMI = 8, > >> + ADV748X_PORT_TTL = 9, > >> + ADV748X_PORT_TXA = 10, > >> + ADV748X_PORT_TXB = 11, > >> + ADV748X_PORT_MAX = 12, > >> +}; > >> + > >> +enum adv748x_csi2_pads { > >> + ADV748X_CSI2_SINK, > >> + ADV748X_CSI2_SOURCE, > >> + ADV748X_CSI2_NR_PADS, > >> +}; > >> + > >> +/* CSI2 transmitters can have 2 internal connections, HDMI/AFE */ > >> +#define ADV748X_CSI2_MAX_SUBDEVS 2 > >> + > >> +struct adv748x_csi2 { > >> + struct adv748x_state *state; > >> + struct v4l2_mbus_framefmt format; > >> + unsigned int page; > >> + > >> + struct media_pad pads[ADV748X_CSI2_NR_PADS]; > >> + struct v4l2_ctrl_handler ctrl_hdl; > >> + struct v4l2_subdev sd; > >> +}; > >> + > >> +#define notifier_to_csi2(n) container_of(n, struct adv748x_csi2, notifier) > >> +#define adv748x_sd_to_csi2(sd) container_of(sd, struct adv748x_csi2, sd) > >> + > >> +enum adv748x_hdmi_pads { > >> + ADV748X_HDMI_SINK, > >> + ADV748X_HDMI_SOURCE, > >> + ADV748X_HDMI_NR_PADS, > >> +}; > >> + > >> +struct adv748x_hdmi { > >> + struct media_pad pads[ADV748X_HDMI_NR_PADS]; > >> + struct v4l2_ctrl_handler ctrl_hdl; > >> + struct v4l2_subdev sd; > >> + struct v4l2_mbus_framefmt format; > >> + > >> + struct v4l2_dv_timings timings; > >> + struct v4l2_fract aspect_ratio; > >> + > >> + struct { > >> + u8 edid[512]; > >> + u32 present; > >> + unsigned int blocks; > >> + } edid; > >> +}; > >> + > >> +#define adv748x_ctrl_to_hdmi(ctrl) \ > >> + container_of(ctrl->handler, struct adv748x_hdmi, ctrl_hdl) > >> +#define adv748x_sd_to_hdmi(sd) container_of(sd, struct adv748x_hdmi, sd) > >> + > >> +enum adv748x_afe_pads { > >> + ADV748X_AFE_SINK_AIN0, > >> + ADV748X_AFE_SINK_AIN1, > >> + ADV748X_AFE_SINK_AIN2, > >> + ADV748X_AFE_SINK_AIN3, > >> + ADV748X_AFE_SINK_AIN4, > >> + ADV748X_AFE_SINK_AIN5, > >> + ADV748X_AFE_SINK_AIN6, > >> + ADV748X_AFE_SINK_AIN7, > >> + ADV748X_AFE_SOURCE, > >> + ADV748X_AFE_NR_PADS, > >> +}; > >> + > >> +struct adv748x_afe { > >> + struct media_pad pads[ADV748X_AFE_NR_PADS]; > >> + struct v4l2_ctrl_handler ctrl_hdl; > >> + struct v4l2_subdev sd; > >> + struct v4l2_mbus_framefmt format; > >> + > >> + bool streaming; > >> + v4l2_std_id curr_norm; > >> + unsigned int input; > >> +}; > >> + > >> +#define adv748x_ctrl_to_afe(ctrl) \ > >> + container_of(ctrl->handler, struct adv748x_afe, ctrl_hdl) > >> +#define adv748x_sd_to_afe(sd) container_of(sd, struct adv748x_afe, sd) > >> + > >> +/** > >> + * struct adv748x_state - State of ADV748X > >> + * @dev: (OF) device > >> + * @client: I2C client > >> + * @mutex: protect global state > >> + * > >> + * @endpoints: parsed device node endpoints for each port > >> + * > >> + * @i2c_addresses I2C Page addresses > >> + * @i2c_clients I2C clients for the page accesses > >> + * @regmap regmap configuration pages. > >> + * > >> + * @hdmi: state of HDMI receiver context > >> + * @afe: state of AFE receiver context > >> + * @txa: state of TXA transmitter context > >> + * @txb: state of TXB transmitter context > >> + */ > >> +struct adv748x_state { > >> + struct device *dev; > >> + struct i2c_client *client; > >> + struct mutex mutex; > >> + > >> + struct device_node *endpoints[ADV748X_PORT_MAX]; > >> + > >> + struct i2c_client *i2c_clients[ADV748X_PAGE_MAX]; > >> + struct regmap *regmap[ADV748X_PAGE_MAX]; > >> + > >> + struct adv748x_hdmi hdmi; > >> + struct adv748x_afe afe; > >> + struct adv748x_csi2 txa; > >> + struct adv748x_csi2 txb; > >> +}; > >> + > >> +#define adv748x_hdmi_to_state(h) container_of(h, struct adv748x_state, hdmi) > >> +#define adv748x_afe_to_state(a) container_of(a, struct adv748x_state, afe) > >> + > >> +#define adv_err(a, fmt, arg...) dev_err(a->dev, fmt, ##arg) > >> +#define adv_info(a, fmt, arg...) dev_info(a->dev, fmt, ##arg) > >> +#define adv_dbg(a, fmt, arg...) dev_dbg(a->dev, fmt, ##arg) > >> + > >> +/* Register Mappings */ > >> + > >> +/* IO Map */ > >> +#define ADV748X_IO_PD 0x00 /* power down controls */ > >> +#define ADV748X_IO_PD_RX_EN BIT(6) > >> + > >> +#define ADV748X_IO_REG_04 0x04 > >> +#define ADV748X_IO_REG_04_FORCE_FR BIT(0) /* Force CP free-run */ > >> + > >> +#define ADV748X_IO_DATAPATH 0x03 /* datapath cntrl */ > >> +#define ADV748X_IO_DATAPATH_VFREQ_M 0x70 > >> +#define ADV748X_IO_DATAPATH_VFREQ_SHIFT 4 > >> + > >> +#define ADV748X_IO_VID_STD 0x05 > >> + > >> +#define ADV748X_IO_10 0x10 /* io_reg_10 */ > >> +#define ADV748X_IO_10_CSI4_EN BIT(7) > >> +#define ADV748X_IO_10_CSI1_EN BIT(6) > >> +#define ADV748X_IO_10_PIX_OUT_EN BIT(5) > >> + > >> +#define ADV748X_IO_CHIP_REV_ID_1 0xdf > >> +#define ADV748X_IO_CHIP_REV_ID_2 0xe0 > >> + > >> +#define ADV748X_IO_SLAVE_ADDR_BASE 0xf2 > >> + > >> +/* HDMI RX Map */ > >> +#define ADV748X_HDMI_LW1 0x07 /* line width_1 */ > >> +#define ADV748X_HDMI_LW1_VERT_FILTER BIT(7) > >> +#define ADV748X_HDMI_LW1_DE_REGEN BIT(5) > >> +#define ADV748X_HDMI_LW1_WIDTH_MASK 0x1fff > >> + > >> +#define ADV748X_HDMI_F0H1 0x09 /* field0 height_1 */ > >> +#define ADV748X_HDMI_F0H1_HEIGHT_MASK 0x1fff > >> + > >> +#define ADV748X_HDMI_F1H1 0x0b /* field1 height_1 */ > >> +#define ADV748X_HDMI_F1H1_INTERLACED BIT(5) > >> + > >> +#define ADV748X_HDMI_HFRONT_PORCH 0x20 /* hsync_front_porch_1 */ > >> +#define ADV748X_HDMI_HFRONT_PORCH_MASK 0x1fff > >> + > >> +#define ADV748X_HDMI_HSYNC_WIDTH 0x22 /* hsync_pulse_width_1 */ > >> +#define ADV748X_HDMI_HSYNC_WIDTH_MASK 0x1fff > >> + > >> +#define ADV748X_HDMI_HBACK_PORCH 0x24 /* hsync_back_porch_1 */ > >> +#define ADV748X_HDMI_HBACK_PORCH_MASK 0x1fff > >> + > >> +#define ADV748X_HDMI_VFRONT_PORCH 0x2a /* field0_vs_front_porch_1 */ > >> +#define ADV748X_HDMI_VFRONT_PORCH_MASK 0x3fff > >> + > >> +#define ADV748X_HDMI_VSYNC_WIDTH 0x2e /* field0_vs_pulse_width_1 */ > >> +#define ADV748X_HDMI_VSYNC_WIDTH_MASK 0x3fff > >> + > >> +#define ADV748X_HDMI_VBACK_PORCH 0x32 /* field0_vs_back_porch_1 */ > >> +#define ADV748X_HDMI_VBACK_PORCH_MASK 0x3fff > >> + > >> +#define ADV748X_HDMI_TMDS_1 0x51 /* hdmi_reg_51 */ > >> +#define ADV748X_HDMI_TMDS_2 0x52 /* hdmi_reg_52 */ > >> + > >> +/* HDMI RX Repeater Map */ > >> +#define ADV748X_REPEATER_EDID_SZ 0x70 /* primary_edid_size */ > >> +#define ADV748X_REPEATER_EDID_SZ_SHIFT 4 > >> + > >> +#define ADV748X_REPEATER_EDID_CTL 0x74 /* hdcp edid controls */ > >> +#define ADV748X_REPEATER_EDID_CTL_EN BIT(0) /* man_edid_a_enable */ > >> + > >> +/* SDP Main Map */ > >> +#define ADV748X_SDP_INSEL 0x00 /* user_map_rw_reg_00 */ > >> + > >> +#define ADV748X_SDP_VID_SEL 0x02 /* user_map_rw_reg_02 */ > >> +#define ADV748X_SDP_VID_SEL_MASK 0xf0 > >> +#define ADV748X_SDP_VID_SEL_SHIFT 4 > >> + > >> +/* Contrast - Unsigned*/ > >> +#define ADV748X_SDP_CON 0x08 /* user_map_rw_reg_08 */ > >> +#define ADV748X_SDP_CON_MIN 0 > >> +#define ADV748X_SDP_CON_DEF 128 > >> +#define ADV748X_SDP_CON_MAX 255 > >> + > >> +/* Brightness - Signed */ > >> +#define ADV748X_SDP_BRI 0x0a /* user_map_rw_reg_0a */ > >> +#define ADV748X_SDP_BRI_MIN -128 > >> +#define ADV748X_SDP_BRI_DEF 0 > >> +#define ADV748X_SDP_BRI_MAX 127 > >> + > >> +/* Hue - Signed, inverted*/ > >> +#define ADV748X_SDP_HUE 0x0b /* user_map_rw_reg_0b */ > >> +#define ADV748X_SDP_HUE_MIN -127 > >> +#define ADV748X_SDP_HUE_DEF 0 > >> +#define ADV748X_SDP_HUE_MAX 128 > >> + > >> +/* Test Patterns / Default Values */ > >> +#define ADV748X_SDP_DEF 0x0c /* user_map_rw_reg_0c */ > >> +#define ADV748X_SDP_DEF_VAL_EN BIT(0) /* Force free run mode */ > >> +#define ADV748X_SDP_DEF_VAL_AUTO_EN BIT(1) /* Free run when no signal */ > >> + > >> +#define ADV748X_SDP_MAP_SEL 0x0e /* user_map_rw_reg_0e */ > >> +#define ADV748X_SDP_MAP_SEL_RO_MAIN 1 > >> + > >> +/* Free run pattern select */ > >> +#define ADV748X_SDP_FRP 0x14 > >> +#define ADV748X_SDP_FRP_MASK GENMASK(3, 1) > >> + > >> +/* Saturation */ > >> +#define ADV748X_SDP_SD_SAT_U 0xe3 /* user_map_rw_reg_e3 */ > >> +#define ADV748X_SDP_SD_SAT_V 0xe4 /* user_map_rw_reg_e4 */ > >> +#define ADV748X_SDP_SAT_MIN 0 > >> +#define ADV748X_SDP_SAT_DEF 128 > >> +#define ADV748X_SDP_SAT_MAX 255 > >> + > >> +/* SDP RO Main Map */ > >> +#define ADV748X_SDP_RO_10 0x10 > >> +#define ADV748X_SDP_RO_10_IN_LOCK BIT(0) > >> + > >> +/* CP Map */ > >> +#define ADV748X_CP_PAT_GEN 0x37 /* int_pat_gen_1 */ > >> +#define ADV748X_CP_PAT_GEN_EN BIT(7) > >> + > >> +/* Contrast Control - Unsigned */ > >> +#define ADV748X_CP_CON 0x3a /* contrast_cntrl */ > >> +#define ADV748X_CP_CON_MIN 0 /* Minimum contrast */ > >> +#define ADV748X_CP_CON_DEF 128 /* Default */ > >> +#define ADV748X_CP_CON_MAX 255 /* Maximum contrast */ > >> + > >> +/* Saturation Control - Unsigned */ > >> +#define ADV748X_CP_SAT 0x3b /* saturation_cntrl */ > >> +#define ADV748X_CP_SAT_MIN 0 /* Minimum saturation */ > >> +#define ADV748X_CP_SAT_DEF 128 /* Default */ > >> +#define ADV748X_CP_SAT_MAX 255 /* Maximum saturation */ > >> + > >> +/* Brightness Control - Signed */ > >> +#define ADV748X_CP_BRI 0x3c /* brightness_cntrl */ > >> +#define ADV748X_CP_BRI_MIN -128 /* Luma is -512d */ > >> +#define ADV748X_CP_BRI_DEF 0 /* Luma is 0 */ > >> +#define ADV748X_CP_BRI_MAX 127 /* Luma is 508d */ > >> + > >> +/* Hue Control */ > >> +#define ADV748X_CP_HUE 0x3d /* hue_cntrl */ > >> +#define ADV748X_CP_HUE_MIN 0 /* -90 degree */ > >> +#define ADV748X_CP_HUE_DEF 0 /* -90 degree */ > >> +#define ADV748X_CP_HUE_MAX 255 /* +90 degree */ > >> + > >> +#define ADV748X_CP_VID_ADJ 0x3e /* vid_adj_0 */ > >> +#define ADV748X_CP_VID_ADJ_ENABLE BIT(7) /* Enable colour controls */ > >> + > >> +#define ADV748X_CP_DE_POS_HIGH 0x8b /* de_pos_adj_6 */ > >> +#define ADV748X_CP_DE_POS_HIGH_SET BIT(6) > >> +#define ADV748X_CP_DE_POS_END_LOW 0x8c /* de_pos_adj_7 */ > >> +#define ADV748X_CP_DE_POS_START_LOW 0x8d /* de_pos_adj_8 */ > >> + > >> +#define ADV748X_CP_VID_ADJ_2 0x91 > >> +#define ADV748X_CP_VID_ADJ_2_INTERLACED BIT(6) > >> +#define ADV748X_CP_VID_ADJ_2_INTERLACED_3D BIT(4) > >> + > >> +#define ADV748X_CP_CLMP_POS 0xc9 /* clmp_pos_cntrl_4 */ > >> +#define ADV748X_CP_CLMP_POS_DIS_AUTO BIT(0) /* dis_auto_param_buff */ > >> + > >> +/* CSI : TXA/TXB Maps */ > >> +#define ADV748X_CSI_VC_REF 0x0d /* csi_tx_top_reg_0d */ > >> +#define ADV748X_CSI_VC_REF_SHIFT 6 > >> + > >> +#define ADV748X_CSI_FS_AS_LS 0x1e /* csi_tx_top_reg_1e */ > >> +#define ADV748X_CSI_FS_AS_LS_UNKNOWN BIT(6) /* Undocumented bit */ > >> + > >> +/* Register handling */ > >> + > >> +int adv748x_read(struct adv748x_state *state, u8 addr, u8 reg); > >> +int adv748x_write(struct adv748x_state *state, u8 page, u8 reg, u8 value); > >> +int adv748x_write_block(struct adv748x_state *state, int client_page, > >> + unsigned int init_reg, const void *val, > >> + size_t val_len); > >> + > >> +#define io_read(s, r) adv748x_read(s, ADV748X_PAGE_IO, r) > >> +#define io_write(s, r, v) adv748x_write(s, ADV748X_PAGE_IO, r, v) > >> +#define io_clrset(s, r, m, v) io_write(s, r, (io_read(s, r) & ~m) | v) > >> + > >> +#define hdmi_read(s, r) adv748x_read(s, ADV748X_PAGE_HDMI, r) > >> +#define hdmi_read16(s, r, m) (((hdmi_read(s, r) << 8) | hdmi_read(s, r+1)) & m) > >> +#define hdmi_write(s, r, v) adv748x_write(s, ADV748X_PAGE_HDMI, r, v) > >> + > >> +#define repeater_read(s, r) adv748x_read(s, ADV748X_PAGE_REPEATER, r) > >> +#define repeater_write(s, r, v) adv748x_write(s, ADV748X_PAGE_REPEATER, r, v) > >> + > >> +#define sdp_read(s, r) adv748x_read(s, ADV748X_PAGE_SDP, r) > >> +#define sdp_write(s, r, v) adv748x_write(s, ADV748X_PAGE_SDP, r, v) > >> +#define sdp_clrset(s, r, m, v) sdp_write(s, r, (sdp_read(s, r) & ~m) | v) > >> + > >> +#define cp_read(s, r) adv748x_read(s, ADV748X_PAGE_CP, r) > >> +#define cp_write(s, r, v) adv748x_write(s, ADV748X_PAGE_CP, r, v) > >> +#define cp_clrset(s, r, m, v) cp_write(s, r, (cp_read(s, r) & ~m) | v) > >> + > >> +#define txa_read(s, r) adv748x_read(s, ADV748X_PAGE_TXA, r) > >> +#define txb_read(s, r) adv748x_read(s, ADV748X_PAGE_TXB, r) > >> + > >> +#define tx_read(t, r) adv748x_read(t->state, t->page, r) > >> +#define tx_write(t, r, v) adv748x_write(t->state, t->page, r, v) > >> + > >> +static inline struct v4l2_subdev *adv748x_get_remote_sd(struct media_pad *pad) > >> +{ > >> + pad = media_entity_remote_pad(pad); > >> + if (!pad) > >> + return NULL; > >> + > >> + return media_entity_to_v4l2_subdev(pad->entity); > >> +} > >> + > >> +void adv748x_subdev_init(struct v4l2_subdev *sd, struct adv748x_state *state, > >> + const struct v4l2_subdev_ops *ops, u32 function, > >> + const char *ident); > >> + > >> +int adv748x_register_subdevs(struct adv748x_state *state, > >> + struct v4l2_device *v4l2_dev); > >> + > >> +int adv748x_txa_power(struct adv748x_state *state, bool on); > >> +int adv748x_txb_power(struct adv748x_state *state, bool on); > >> + > >> +int adv748x_afe_init(struct adv748x_afe *afe); > >> +void adv748x_afe_cleanup(struct adv748x_afe *afe); > >> + > >> +int adv748x_csi2_init(struct adv748x_state *state, struct adv748x_csi2 *tx); > >> +void adv748x_csi2_cleanup(struct adv748x_csi2 *tx); > >> +int adv748x_csi2_set_pixelrate(struct v4l2_subdev *sd, s64 rate); > >> + > >> +int adv748x_hdmi_init(struct adv748x_hdmi *hdmi); > >> +void adv748x_hdmi_cleanup(struct adv748x_hdmi *hdmi); > >> + > >> +#endif /* _ADV748X_H_ */ > >> -- > >> git-series 0.9.1 > > -- Regards, Niklas Söderlund