summaryrefslogtreecommitdiffstats
path: root/drivers/media/i2c/adv748x
diff options
context:
space:
mode:
authorKieran Bingham <kieran.bingham+renesas@ideasonboard.com>2017-07-06 07:01:16 -0400
committerMauro Carvalho Chehab <mchehab@s-opensource.com>2017-07-19 16:35:31 -0400
commit3e89586a64dfd2860d596db0c84ec999d2eb5591 (patch)
treec00aed3f490d183401507fb6ae9c5273c9a402b0 /drivers/media/i2c/adv748x
parente69595170b1cad850b5076656b2e1542f3eb2437 (diff)
media: i2c: adv748x: add adv748x driver
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@ideasonboard.com> Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com> Signed-off-by: Mauro Carvalho Chehab <mchehab@s-opensource.com>
Diffstat (limited to 'drivers/media/i2c/adv748x')
-rw-r--r--drivers/media/i2c/adv748x/Makefile7
-rw-r--r--drivers/media/i2c/adv748x/adv748x-afe.c552
-rw-r--r--drivers/media/i2c/adv748x/adv748x-core.c832
-rw-r--r--drivers/media/i2c/adv748x/adv748x-csi2.c327
-rw-r--r--drivers/media/i2c/adv748x/adv748x-hdmi.c768
-rw-r--r--drivers/media/i2c/adv748x/adv748x.h425
6 files changed, 2911 insertions, 0 deletions
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@renesas.com>
+ * Niklas Söderlund <niklas.soderlund@ragnatech.se>
+ * Kieran Bingham <kieran.bingham@ideasonboard.com>
+ */
+
+#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}, /* A