Hi Kieran, Thanks for your hard work. 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. 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. 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