summaryrefslogtreecommitdiffstats
path: root/drivers/media/i2c/tda1997x.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/media/i2c/tda1997x.c')
-rw-r--r--drivers/media/i2c/tda1997x.c2820
1 files changed, 2820 insertions, 0 deletions
diff --git a/drivers/media/i2c/tda1997x.c b/drivers/media/i2c/tda1997x.c
new file mode 100644
index 000000000000..3021913c28fa
--- /dev/null
+++ b/drivers/media/i2c/tda1997x.c
@@ -0,0 +1,2820 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 Gateworks Corporation
+ */
+#include <linux/delay.h>
+#include <linux/hdmi.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of_graph.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/types.h>
+#include <linux/v4l2-dv-timings.h>
+#include <linux/videodev2.h>
+
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-dv-timings.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-fwnode.h>
+#include <media/i2c/tda1997x.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <dt-bindings/media/tda1997x.h>
+
+#include "tda1997x_regs.h"
+
+#define TDA1997X_MBUS_CODES 5
+
+/* debug level */
+static int debug;
+module_param(debug, int, 0644);
+MODULE_PARM_DESC(debug, "debug level (0-2)");
+
+/* Audio formats */
+static const char * const audtype_names[] = {
+ "PCM", /* PCM Samples */
+ "HBR", /* High Bit Rate Audio */
+ "OBA", /* One-Bit Audio */
+ "DST" /* Direct Stream Transfer */
+};
+
+/* Audio output port formats */
+enum audfmt_types {
+ AUDFMT_TYPE_DISABLED = 0,
+ AUDFMT_TYPE_I2S,
+ AUDFMT_TYPE_SPDIF,
+};
+static const char * const audfmt_names[] = {
+ "Disabled",
+ "I2S",
+ "SPDIF",
+};
+
+/* Video input formats */
+static const char * const hdmi_colorspace_names[] = {
+ "RGB", "YUV422", "YUV444", "YUV420", "", "", "", "",
+};
+static const char * const hdmi_colorimetry_names[] = {
+ "", "ITU601", "ITU709", "Extended",
+};
+static const char * const v4l2_quantization_names[] = {
+ "Default",
+ "Full Range (0-255)",
+ "Limited Range (16-235)",
+};
+
+/* Video output port formats */
+static const char * const vidfmt_names[] = {
+ "RGB444/YUV444", /* RGB/YUV444 16bit data bus, 8bpp */
+ "YUV422 semi-planar", /* YUV422 16bit data base, 8bpp */
+ "YUV422 CCIR656", /* BT656 (YUV 8bpp 2 clock per pixel) */
+ "Invalid",
+};
+
+/*
+ * Colorspace conversion matrices
+ */
+struct color_matrix_coefs {
+ const char *name;
+ /* Input offsets */
+ s16 offint1;
+ s16 offint2;
+ s16 offint3;
+ /* Coeficients */
+ s16 p11coef;
+ s16 p12coef;
+ s16 p13coef;
+ s16 p21coef;
+ s16 p22coef;
+ s16 p23coef;
+ s16 p31coef;
+ s16 p32coef;
+ s16 p33coef;
+ /* Output offsets */
+ s16 offout1;
+ s16 offout2;
+ s16 offout3;
+};
+
+enum {
+ ITU709_RGBFULL,
+ ITU601_RGBFULL,
+ RGBLIMITED_RGBFULL,
+ RGBLIMITED_ITU601,
+ RGBLIMITED_ITU709,
+ RGBFULL_ITU601,
+ RGBFULL_ITU709,
+};
+
+/* NB: 4096 is 1.0 using fixed point numbers */
+static const struct color_matrix_coefs conv_matrix[] = {
+ {
+ "YUV709 -> RGB full",
+ -256, -2048, -2048,
+ 4769, -2183, -873,
+ 4769, 7343, 0,
+ 4769, 0, 8652,
+ 0, 0, 0,
+ },
+ {
+ "YUV601 -> RGB full",
+ -256, -2048, -2048,
+ 4769, -3330, -1602,
+ 4769, 6538, 0,
+ 4769, 0, 8264,
+ 256, 256, 256,
+ },
+ {
+ "RGB limited -> RGB full",
+ -256, -256, -256,
+ 0, 4769, 0,
+ 0, 0, 4769,
+ 4769, 0, 0,
+ 0, 0, 0,
+ },
+ {
+ "RGB limited -> ITU601",
+ -256, -256, -256,
+ 2404, 1225, 467,
+ -1754, 2095, -341,
+ -1388, -707, 2095,
+ 256, 2048, 2048,
+ },
+ {
+ "RGB limited -> ITU709",
+ -256, -256, -256,
+ 2918, 867, 295,
+ -1894, 2087, -190,
+ -1607, -477, 2087,
+ 256, 2048, 2048,
+ },
+ {
+ "RGB full -> ITU601",
+ 0, 0, 0,
+ 2065, 1052, 401,
+ -1506, 1799, -293,
+ -1192, -607, 1799,
+ 256, 2048, 2048,
+ },
+ {
+ "RGB full -> ITU709",
+ 0, 0, 0,
+ 2506, 745, 253,
+ -1627, 1792, -163,
+ -1380, -410, 1792,
+ 256, 2048, 2048,
+ },
+};
+
+static const struct v4l2_dv_timings_cap tda1997x_dv_timings_cap = {
+ .type = V4L2_DV_BT_656_1120,
+ /* keep this initialization for compatibility with GCC < 4.4.6 */
+ .reserved = { 0 },
+
+ V4L2_INIT_BT_TIMINGS(
+ 640, 1920, /* min/max width */
+ 350, 1200, /* min/max height */
+ 13000000, 165000000, /* min/max pixelclock */
+ /* standards */
+ V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT |
+ V4L2_DV_BT_STD_GTF | V4L2_DV_BT_STD_CVT,
+ /* capabilities */
+ V4L2_DV_BT_CAP_INTERLACED | V4L2_DV_BT_CAP_PROGRESSIVE |
+ V4L2_DV_BT_CAP_REDUCED_BLANKING |
+ V4L2_DV_BT_CAP_CUSTOM
+ )
+};
+
+/* regulator supplies */
+static const char * const tda1997x_supply_name[] = {
+ "DOVDD", /* Digital I/O supply */
+ "DVDD", /* Digital Core supply */
+ "AVDD", /* Analog supply */
+};
+
+#define TDA1997X_NUM_SUPPLIES ARRAY_SIZE(tda1997x_supply_name)
+
+enum tda1997x_type {
+ TDA19971,
+ TDA19973,
+};
+
+enum tda1997x_hdmi_pads {
+ TDA1997X_PAD_SOURCE,
+ TDA1997X_NUM_PADS,
+};
+
+struct tda1997x_chip_info {
+ enum tda1997x_type type;
+ const char *name;
+};
+
+struct tda1997x_state {
+ const struct tda1997x_chip_info *info;
+ struct tda1997x_platform_data pdata;
+ struct i2c_client *client;
+ struct i2c_client *client_cec;
+ struct v4l2_subdev sd;
+ struct regulator_bulk_data supplies[TDA1997X_NUM_SUPPLIES];
+ struct media_pad pads[TDA1997X_NUM_PADS];
+ struct mutex lock;
+ struct mutex page_lock;
+ char page;
+
+ /* detected info from chip */
+ int chip_revision;
+ char port_30bit;
+ char output_2p5;
+ char tmdsb_clk;
+ char tmdsb_soc;
+
+ /* status info */
+ char hdmi_status;
+ char mptrw_in_progress;
+ char activity_status;
+ char input_detect[2];
+
+ /* video */
+ struct hdmi_avi_infoframe avi_infoframe;
+ struct v4l2_hdmi_colorimetry colorimetry;
+ u32 rgb_quantization_range;
+ struct v4l2_dv_timings timings;
+ int fps;
+ const struct color_matrix_coefs *conv;
+ u32 mbus_codes[TDA1997X_MBUS_CODES]; /* available modes */
+ u32 mbus_code; /* current mode */
+ u8 vid_fmt;
+
+ /* controls */
+ struct v4l2_ctrl_handler hdl;
+ struct v4l2_ctrl *detect_tx_5v_ctrl;
+ struct v4l2_ctrl *rgb_quantization_range_ctrl;
+
+ /* audio */
+ u8 audio_ch_alloc;
+ int audio_samplerate;
+ int audio_channels;
+ int audio_samplesize;
+ int audio_type;
+ struct mutex audio_lock;
+ struct snd_pcm_substream *audio_stream;
+
+ /* EDID */
+ struct {
+ u8 edid[256];
+ u32 present;
+ unsigned int blocks;
+ } edid;
+ struct delayed_work delayed_work_enable_hpd;
+};
+
+static const struct v4l2_event tda1997x_ev_fmt = {
+ .type = V4L2_EVENT_SOURCE_CHANGE,
+ .u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION,
+};
+
+static const struct tda1997x_chip_info tda1997x_chip_info[] = {
+ [TDA19971] = {
+ .type = TDA19971,
+ .name = "tda19971",
+ },
+ [TDA19973] = {
+ .type = TDA19973,
+ .name = "tda19973",
+ },
+};
+
+static inline struct tda1997x_state *to_state(struct v4l2_subdev *sd)
+{
+ return container_of(sd, struct tda1997x_state, sd);
+}
+
+static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl)
+{
+ return &container_of(ctrl->handler, struct tda1997x_state, hdl)->sd;
+}
+
+static int tda1997x_cec_read(struct v4l2_subdev *sd, u8 reg)
+{
+ struct tda1997x_state *state = to_state(sd);
+ int val;
+
+ val = i2c_smbus_read_byte_data(state->client_cec, reg);
+ if (val < 0) {
+ v4l_err(state->client, "read reg error: reg=%2x\n", reg);
+ val = -1;
+ }
+
+ return val;
+}
+
+static int tda1997x_cec_write(struct v4l2_subdev *sd, u8 reg, u8 val)
+{
+ struct tda1997x_state *state = to_state(sd);
+ int ret = 0;
+
+ ret = i2c_smbus_write_byte_data(state->client_cec, reg, val);
+ if (ret < 0) {
+ v4l_err(state->client, "write reg error:reg=%2x,val=%2x\n",
+ reg, val);
+ ret = -1;
+ }
+
+ return ret;
+}
+
+/* -----------------------------------------------------------------------------
+ * I2C transfer
+ */
+
+static int tda1997x_setpage(struct v4l2_subdev *sd, u8 page)
+{
+ struct tda1997x_state *state = to_state(sd);
+ int ret;
+
+ if (state->page != page) {
+ ret = i2c_smbus_write_byte_data(state->client,
+ REG_CURPAGE_00H, page);
+ if (ret < 0) {
+ v4l_err(state->client,
+ "write reg error:reg=%2x,val=%2x\n",
+ REG_CURPAGE_00H, page);
+ return ret;
+ }
+ state->page = page;
+ }
+ return 0;
+}
+
+static inline int io_read(struct v4l2_subdev *sd, u16 reg)
+{
+ struct tda1997x_state *state = to_state(sd);
+ int val;
+
+ mutex_lock(&state->page_lock);
+ if (tda1997x_setpage(sd, reg >> 8)) {
+ val = -1;
+ goto out;
+ }
+
+ val = i2c_smbus_read_byte_data(state->client, reg&0xff);
+ if (val < 0) {
+ v4l_err(state->client, "read reg error: reg=%2x\n", reg & 0xff);
+ val = -1;
+ goto out;
+ }
+
+out:
+ mutex_unlock(&state->page_lock);
+ return val;
+}
+
+static inline long io_read16(struct v4l2_subdev *sd, u16 reg)
+{
+ int val;
+ long lval = 0;
+
+ val = io_read(sd, reg);
+ if (val < 0)
+ return val;
+ lval |= (val << 8);
+ val = io_read(sd, reg + 1);
+ if (val < 0)
+ return val;
+ lval |= val;
+
+ return lval;
+}
+
+static inline long io_read24(struct v4l2_subdev *sd, u16 reg)
+{
+ int val;
+ long lval = 0;
+
+ val = io_read(sd, reg);
+ if (val < 0)
+ return val;
+ lval |= (val << 16);
+ val = io_read(sd, reg + 1);
+ if (val < 0)
+ return val;
+ lval |= (val << 8);
+ val = io_read(sd, reg + 2);
+ if (val < 0)
+ return val;
+ lval |= val;
+
+ return lval;
+}
+
+static unsigned int io_readn(struct v4l2_subdev *sd, u16 reg, u8 len, u8 *data)
+{
+ int i;
+ int sz = 0;
+ int val;
+
+ for (i = 0; i < len; i++) {
+ val = io_read(sd, reg + i);
+ if (val < 0)
+ break;
+ data[i] = val;
+ sz++;
+ }
+
+ return sz;
+}
+
+static int io_write(struct v4l2_subdev *sd, u16 reg, u8 val)
+{
+ struct tda1997x_state *state = to_state(sd);
+ s32 ret = 0;
+
+ mutex_lock(&state->page_lock);
+ if (tda1997x_setpage(sd, reg >> 8)) {
+ ret = -1;
+ goto out;
+ }
+
+ ret = i2c_smbus_write_byte_data(state->client, reg & 0xff, val);
+ if (ret < 0) {
+ v4l_err(state->client, "write reg error:reg=%2x,val=%2x\n",
+ reg&0xff, val);
+ ret = -1;
+ goto out;
+ }
+
+out:
+ mutex_unlock(&state->page_lock);
+ return ret;
+}
+
+static int io_write16(struct v4l2_subdev *sd, u16 reg, u16 val)
+{
+ int ret;
+
+ ret = io_write(sd, reg, (val >> 8) & 0xff);
+ if (ret < 0)
+ return ret;
+ ret = io_write(sd, reg + 1, val & 0xff);
+ if (ret < 0)
+ return ret;
+ return 0;
+}
+
+static int io_write24(struct v4l2_subdev *sd, u16 reg, u32 val)
+{
+ int ret;
+
+ ret = io_write(sd, reg, (val >> 16) & 0xff);
+ if (ret < 0)
+ return ret;
+ ret = io_write(sd, reg + 1, (val >> 8) & 0xff);
+ if (ret < 0)
+ return ret;
+ ret = io_write(sd, reg + 2, val & 0xff);
+ if (ret < 0)
+ return ret;
+ return 0;
+}
+
+/* -----------------------------------------------------------------------------
+ * Hotplug
+ */
+
+enum hpd_mode {
+ HPD_LOW_BP, /* HPD low and pulse of at least 100ms */
+ HPD_LOW_OTHER, /* HPD low and pulse of at least 100ms */
+ HPD_HIGH_BP, /* HIGH */
+ HPD_HIGH_OTHER,
+ HPD_PULSE, /* HPD low pulse */
+};
+
+/* manual HPD (Hot Plug Detect) control */
+static int tda1997x_manual_hpd(struct v4l2_subdev *sd, enum hpd_mode mode)
+{
+ u8 hpd_auto, hpd_pwr, hpd_man;
+
+ hpd_auto = io_read(sd, REG_HPD_AUTO_CTRL);
+ hpd_pwr = io_read(sd, REG_HPD_POWER);
+ hpd_man = io_read(sd, REG_HPD_MAN_CTRL);
+
+ /* mask out unused bits */
+ hpd_man &= (HPD_MAN_CTRL_HPD_PULSE |
+ HPD_MAN_CTRL_5VEN |
+ HPD_MAN_CTRL_HPD_B |
+ HPD_MAN_CTRL_HPD_A);
+
+ switch (mode) {
+ /* HPD low and pulse of at least 100ms */
+ case HPD_LOW_BP:
+ /* hpd_bp=0 */
+ hpd_pwr &= ~HPD_POWER_BP_MASK;
+ /* disable HPD_A and HPD_B */
+ hpd_man &= ~(HPD_MAN_CTRL_HPD_A | HPD_MAN_CTRL_HPD_B);
+ io_write(sd, REG_HPD_POWER, hpd_pwr);
+ io_write(sd, REG_HPD_MAN_CTRL, hpd_man);
+ break;
+ /* HPD high */
+ case HPD_HIGH_BP:
+ /* hpd_bp=1 */
+ hpd_pwr &= ~HPD_POWER_BP_MASK;
+ hpd_pwr |= 1 << HPD_POWER_BP_SHIFT;
+ io_write(sd, REG_HPD_POWER, hpd_pwr);
+ break;
+ /* HPD low and pulse of at least 100ms */
+ case HPD_LOW_OTHER:
+ /* disable HPD_A and HPD_B */
+ hpd_man &= ~(HPD_MAN_CTRL_HPD_A | HPD_MAN_CTRL_HPD_B);
+ /* hp_other=0 */
+ hpd_auto &= ~HPD_AUTO_HP_OTHER;
+ io_write(sd, REG_HPD_AUTO_CTRL, hpd_auto);
+ io_write(sd, REG_HPD_MAN_CTRL, hpd_man);
+ break;
+ /* HPD high */
+ case HPD_HIGH_OTHER:
+ hpd_auto |= HPD_AUTO_HP_OTHER;
+ io_write(sd, REG_HPD_AUTO_CTRL, hpd_auto);
+ break;
+ /* HPD low pulse */
+ case HPD_PULSE:
+ /* disable HPD_A and HPD_B */
+ hpd_man &= ~(HPD_MAN_CTRL_HPD_A | HPD_MAN_CTRL_HPD_B);
+ io_write(sd, REG_HPD_MAN_CTRL, hpd_man);
+ break;
+ }
+
+ return 0;
+}
+
+static void tda1997x_delayed_work_enable_hpd(struct work_struct *work)
+{
+ struct delayed_work *dwork = to_delayed_work(work);
+ struct tda1997x_state *state = container_of(dwork,
+ struct tda1997x_state,
+ delayed_work_enable_hpd);
+ struct v4l2_subdev *sd = &state->sd;
+
+ v4l2_dbg(2, debug, sd, "%s:\n", __func__);
+
+ /* Set HPD high */
+ tda1997x_manual_hpd(sd, HPD_HIGH_OTHER);
+ tda1997x_manual_hpd(sd, HPD_HIGH_BP);
+
+ state->edid.present = 1;
+}
+
+static void tda1997x_disable_edid(struct v4l2_subdev *sd)
+{
+ struct tda1997x_state *state = to_state(sd);
+
+ v4l2_dbg(1, debug, sd, "%s\n", __func__);
+ cancel_delayed_work_sync(&state->delayed_work_enable_hpd);
+
+ /* Set HPD low */
+ tda1997x_manual_hpd(sd, HPD_LOW_BP);
+}
+
+static void tda1997x_enable_edid(struct v4l2_subdev *sd)
+{
+ struct tda1997x_state *state = to_state(sd);
+
+ v4l2_dbg(1, debug, sd, "%s\n", __func__);
+
+ /* Enable hotplug after 100ms */
+ schedule_delayed_work(&state->delayed_work_enable_hpd, HZ / 10);
+}
+
+/* -----------------------------------------------------------------------------
+ * Signal Control
+ */
+
+/*
+ * configure vid_fmt based on mbus_code
+ */
+static int
+tda1997x_setup_format(struct tda1997x_state *state, u32 code)
+{
+ v4l_dbg(1, debug, state->client, "%s code=0x%x\n", __func__, code);
+ switch (code) {
+ case MEDIA_BUS_FMT_RGB121212_1X36:
+ case MEDIA_BUS_FMT_RGB888_1X24:
+ case MEDIA_BUS_FMT_YUV12_1X36:
+ case MEDIA_BUS_FMT_YUV8_1X24:
+ state->vid_fmt = OF_FMT_444;
+ break;
+ case MEDIA_BUS_FMT_UYVY12_1X24:
+ case MEDIA_BUS_FMT_UYVY10_1X20:
+ case MEDIA_BUS_FMT_UYVY8_1X16:
+ state->vid_fmt = OF_FMT_422_SMPT;
+ break;
+ case MEDIA_BUS_FMT_UYVY12_2X12:
+ case MEDIA_BUS_FMT_UYVY10_2X10:
+ case MEDIA_BUS_FMT_UYVY8_2X8:
+ state->vid_fmt = OF_FMT_422_CCIR;
+ break;
+ default:
+ v4l_err(state->client, "incompatible format (0x%x)\n", code);
+ return -EINVAL;
+ }
+ v4l_dbg(1, debug, state->client, "%s code=0x%x fmt=%s\n", __func__,
+ code, vidfmt_names[state->vid_fmt]);
+ state->mbus_code = code;
+
+ return 0;
+}
+
+/*
+ * The color conversion matrix will convert between the colorimetry of the
+ * HDMI input to the desired output format RGB|YUV. RGB output is to be
+ * full-range and YUV is to be limited range.
+ *
+ * RGB full-range uses values from 0 to 255 which is recommended on a monitor
+ * and RGB Limited uses values from 16 to 236 (16=black, 235=white) which is
+ * typically recommended on a TV.
+ */
+static void
+tda1997x_configure_csc(struct v4l2_subdev *sd)
+{
+ struct tda1997x_state *state = to_state(sd);
+ struct hdmi_avi_infoframe *avi = &state->avi_infoframe;
+ struct v4l2_hdmi_colorimetry *c = &state->colorimetry;
+ /* Blanking code values depend on output colorspace (RGB or YUV) */
+ struct blanking_codes {
+ s16 code_gy;
+ s16 code_bu;
+ s16 code_rv;
+ };
+ static const struct blanking_codes rgb_blanking = { 64, 64, 64 };
+ static const struct blanking_codes yuv_blanking = { 64, 512, 512 };
+ const struct blanking_codes *blanking_codes = NULL;
+ u8 reg;
+
+ v4l_dbg(1, debug, state->client, "input:%s quant:%s output:%s\n",
+ hdmi_colorspace_names[avi->colorspace],
+ v4l2_quantization_names[c->quantization],
+ vidfmt_names[state->vid_fmt]);
+ state->conv = NULL;
+ switch (state->vid_fmt) {
+ /* RGB output */
+ case OF_FMT_444:
+ blanking_codes = &rgb_blanking;
+ if (c->colorspace == V4L2_COLORSPACE_SRGB) {
+ if (c->quantization == V4L2_QUANTIZATION_LIM_RANGE)
+ state->conv = &conv_matrix[RGBLIMITED_RGBFULL];
+ } else {
+ if (c->colorspace == V4L2_COLORSPACE_REC709)
+ state->conv = &conv_matrix[ITU709_RGBFULL];
+ else if (c->colorspace == V4L2_COLORSPACE_SMPTE170M)
+ state->conv = &conv_matrix[ITU601_RGBFULL];
+ }
+ break;
+
+ /* YUV output */
+ case OF_FMT_422_SMPT: /* semi-planar */
+ case OF_FMT_422_CCIR: /* CCIR656 */
+ blanking_codes = &yuv_blanking;
+ if ((c->colorspace == V4L2_COLORSPACE_SRGB) &&
+ (c->quantization == V4L2_QUANTIZATION_FULL_RANGE)) {
+ if (state->timings.bt.height <= 576)
+ state->conv = &conv_matrix[RGBFULL_ITU601];
+ else
+ state->conv = &conv_matrix[RGBFULL_ITU709];
+ } else if ((c->colorspace == V4L2_COLORSPACE_SRGB) &&
+ (c->quantization == V4L2_QUANTIZATION_LIM_RANGE)) {
+ if (state->timings.bt.height <= 576)
+ state->conv = &conv_matrix[RGBLIMITED_ITU601];
+ else
+ state->conv = &conv_matrix[RGBLIMITED_ITU709];
+ }
+ break;
+ }
+
+ if (state->conv) {
+ v4l_dbg(1, debug, state->client, "%s\n",
+ state->conv->name);
+ /* enable matrix conversion */
+ reg = io_read(sd, REG_VDP_CTRL);
+ reg &= ~VDP_CTRL_MATRIX_BP;
+ io_write(sd, REG_VDP_CTRL, reg);
+ /* offset inputs */
+ io_write16(sd, REG_VDP_MATRIX + 0, state->conv->offint1);
+ io_write16(sd, REG_VDP_MATRIX + 2, state->conv->offint2);
+ io_write16(sd, REG_VDP_MATRIX + 4, state->conv->offint3);
+ /* coefficients */
+ io_write16(sd, REG_VDP_MATRIX + 6, state->conv->p11coef);
+ io_write16(sd, REG_VDP_MATRIX + 8, state->conv->p12coef);
+ io_write16(sd, REG_VDP_MATRIX + 10, state->conv->p13coef);
+ io_write16(sd, REG_VDP_MATRIX + 12, state->conv->p21coef);
+ io_write16(sd, REG_VDP_MATRIX + 14, state->conv->p22coef);
+ io_write16(sd, REG_VDP_MATRIX + 16, state->conv->p23coef);
+ io_write16(sd, REG_VDP_MATRIX + 18, state->conv->p31coef);
+ io_write16(sd, REG_VDP_MATRIX + 20, state->conv->p32coef);
+ io_write16(sd, REG_VDP_MATRIX + 22, state->conv->p33coef);
+ /* offset outputs */
+ io_write16(sd, REG_VDP_MATRIX + 24, state->conv->offout1);
+ io_write16(sd, REG_VDP_MATRIX + 26, state->conv->offout2);
+ io_write16(sd, REG_VDP_MATRIX + 28, state->conv->offout3);
+ } else {
+ /* disable matrix conversion */
+ reg = io_read(sd, REG_VDP_CTRL);
+ reg |= VDP_CTRL_MATRIX_BP;
+ io_write(sd, REG_VDP_CTRL, reg);
+ }
+
+ /* SetBlankingCodes */
+ if (blanking_codes) {
+ io_write16(sd, REG_BLK_GY, blanking_codes->code_gy);
+ io_write16(sd, REG_BLK_BU, blanking_codes->code_bu);
+ io_write16(sd, REG_BLK_RV, blanking_codes->code_rv);
+ }
+}
+
+/* Configure frame detection window and VHREF timing generator */
+static void
+tda1997x_configure_vhref(struct v4l2_subdev *sd)
+{
+ struct tda1997x_state *state = to_state(sd);
+ const struct v4l2_bt_timings *bt = &state->timings.bt;
+ int width, lines;
+ u16 href_start, href_end;
+ u16 vref_f1_start, vref_f2_start;
+ u8 vref_f1_width, vref_f2_width;
+ u8 field_polarity;
+ u16 fieldref_f1_start, fieldref_f2_start;
+ u8 reg;
+
+ href_start = bt->hbackporch + bt->hsync + 1;
+ href_end = href_start + bt->width;
+ vref_f1_start = bt->height + bt->vbackporch + bt->vsync +
+ bt->il_vbackporch + bt->il_vsync +
+ bt->il_vfrontporch;
+ vref_f1_width = bt->vbackporch + bt->vsync + bt->vfrontporch;
+ vref_f2_start = 0;
+ vref_f2_width = 0;
+ fieldref_f1_start = 0;
+ fieldref_f2_start = 0;
+ if (bt->interlaced) {
+ vref_f2_start = (bt->height / 2) +
+ (bt->il_vbackporch + bt->il_vsync - 1);
+ vref_f2_width = bt->il_vbackporch + bt->il_vsync +
+ bt->il_vfrontporch;
+ fieldref_f2_start = vref_f2_start + bt->il_vfrontporch +
+ fieldref_f1_start;
+ }
+ field_polarity = 0;
+
+ width = V4L2_DV_BT_FRAME_WIDTH(bt);
+ lines = V4L2_DV_BT_FRAME_HEIGHT(bt);
+
+ /*
+ * Configure Frame Detection Window:
+ * horiz area where the VHREF module consider a VSYNC a new frame
+ */
+ io_write16(sd, REG_FDW_S, 0x2ef); /* start position */
+ io_write16(sd, REG_FDW_E, 0x141); /* end position */
+
+ /* Set Pixel And Line Counters */
+ if (state->chip_revision == 0)
+ io_write16(sd, REG_PXCNT_PR, 4);
+ else
+ io_write16(sd, REG_PXCNT_PR, 1);
+ io_write16(sd, REG_PXCNT_NPIX, width & MASK_VHREF);
+ io_write16(sd, REG_LCNT_PR, 1);
+ io_write16(sd, REG_LCNT_NLIN, lines & MASK_VHREF);
+
+ /*
+ * Configure the VHRef timing generator responsible for rebuilding all
+ * horiz and vert synch and ref signals from its input allowing auto
+ * detection algorithms and forcing predefined modes (480i & 576i)
+ */
+ reg = VHREF_STD_DET_OFF << VHREF_STD_DET_SHIFT;
+ io_write(sd, REG_VHREF_CTRL, reg);
+
+ /*
+ * Configure the VHRef timing values. In case the VHREF generator has
+ * been configured in manual mode, this will allow to manually set all
+ * horiz and vert ref values (non-active pixel areas) of the generator
+ * and allows setting the frame reference params.
+ */
+ /* horizontal reference start/end */
+ io_write16(sd, REG_HREF_S, href_start & MASK_VHREF);
+ io_write16(sd, REG_HREF_E, href_end & MASK_VHREF);
+ /* vertical reference f1 start/end */
+ io_write16(sd, REG_VREF_F1_S, vref_f1_start & MASK_VHREF);
+ io_write(sd, REG_VREF_F1_WIDTH, vref_f1_width);
+ /* vertical reference f2 start/end */
+ io_write16(sd, REG_VREF_F2_S, vref_f2_start & MASK_VHREF);
+ io_write(sd, REG_VREF_F2_WIDTH, vref_f2_width);
+
+ /* F1/F2 FREF, field polarity */
+ reg = fieldref_f1_start & MASK_VHREF;
+ reg |= field_polarity << 8;
+ io_write16(sd, REG_FREF_F1_S, reg);
+ reg = fieldref_f2_start & MASK_VHREF;
+ io_write16(sd, REG_FREF_F2_S, reg);
+}
+
+/* Configure Video Output port signals */
+static int
+tda1997x_configure_vidout(struct tda1997x_state *state)
+{
+ struct v4l2_subdev *sd = &state->sd;
+ struct tda1997x_platform_data *pdata = &state->pdata;
+ u8 prefilter;
+ u8 reg;
+
+ /* Configure pixel clock generator: delay, polarity, rate */
+ reg = (state->vid_fmt == OF_FMT_422_CCIR) ?
+ PCLK_SEL_X2 : PCLK_SEL_X1;
+ reg |= pdata->vidout_delay_pclk << PCLK_DELAY_SHIFT;
+ reg |= pdata->vidout_inv_pclk << PCLK_INV_SHIFT;
+ io_write(sd, REG_PCLK, reg);
+
+ /* Configure pre-filter */
+ prefilter = 0; /* filters off */
+ /* YUV422 mode requires conversion */
+ if ((state->vid_fmt == OF_FMT_422_SMPT) ||
+ (state->vid_fmt == OF_FMT_422_CCIR)) {
+ /* 2/7 taps for Rv and Bu */
+ prefilter = FILTERS_CTRL_2_7TAP << FILTERS_CTRL_BU_SHIFT |
+ FILTERS_CTRL_2_7TAP << FILTERS_CTRL_RV_SHIFT;
+ }
+ io_write(sd, REG_FILTERS_CTRL, prefilter);
+
+ /* Configure video port */
+ reg = state->vid_fmt & OF_FMT_MASK;
+ if (state->vid_fmt == OF_FMT_422_CCIR)
+ reg |= (OF_BLK | OF_TRC);
+ reg |= OF_VP_ENABLE;
+ io_write(sd, REG_OF, reg);
+
+ /* Configure formatter and conversions */
+ reg = io_read(sd, REG_VDP_CTRL);
+ /* pre-filter is needed unless (REG_FILTERS_CTRL == 0) */
+ if (!prefilter)
+ reg |= VDP_CTRL_PREFILTER_BP;
+ else
+ reg &= ~VDP_CTRL_PREFILTER_BP;
+ /* formatter is needed for YUV422 and for trc/blc codes */
+ if (state->vid_fmt == OF_FMT_444)
+ reg |= VDP_CTRL_FORMATTER_BP;
+ /* formatter and compdel needed for timing/blanking codes */
+ else
+ reg &= ~(VDP_CTRL_FORMATTER_BP | VDP_CTRL_COMPDEL_BP);
+ /* activate compdel for small sync delays */
+ if ((pdata->vidout_delay_vs < 4) || (pdata->vidout_delay_hs < 4))
+ reg &= ~VDP_CTRL_COMPDEL_BP;
+ io_write(sd, REG_VDP_CTRL, reg);
+
+ /* Configure DE output signal: delay, polarity, and source */
+ reg = pdata->vidout_delay_de << DE_FREF_DELAY_SHIFT |
+ pdata->vidout_inv_de << DE_FREF_INV_SHIFT |
+ pdata->vidout_sel_de << DE_FREF_SEL_SHIFT;
+ io_write(sd, REG_DE_FREF, reg);
+
+ /* Configure HS/HREF output signal: delay, polarity, and source */
+ if (state->vid_fmt != OF_FMT_422_CCIR) {
+ reg = pdata->vidout_delay_hs << HS_HREF_DELAY_SHIFT |
+ pdata->vidout_inv_hs << HS_HREF_INV_SHIFT |
+ pdata->vidout_sel_hs << HS_HREF_SEL_SHIFT;
+ } else
+ reg = HS_HREF_SEL_NONE << HS_HREF_SEL_SHIFT;
+ io_write(sd, REG_HS_HREF, reg);
+
+ /* Configure VS/VREF output signal: delay, polarity, and source */
+ if (state->vid_fmt != OF_FMT_422_CCIR) {
+ reg = pdata->vidout_delay_vs << VS_VREF_DELAY_SHIFT |
+ pdata->vidout_inv_vs << VS_VREF_INV_SHIFT |
+ pdata->vidout_sel_vs << VS_VREF_SEL_SHIFT;
+ } else
+ reg = VS_VREF_SEL_NONE << VS_VREF_SEL_SHIFT;
+ io_write(sd, REG_VS_VREF, reg);
+
+ return 0;
+}
+
+/* Configure Audio output port signals */
+static int
+tda1997x_configure_audout(struct v4l2_subdev *sd, u8 channel_assignment)
+{
+ struct tda1997x_state *state = to_state(sd);
+ struct tda1997x_platform_data *pdata = &state->pdata;
+ bool sp_used_by_fifo = 1;
+ u8 reg;
+
+ if (!pdata->audout_format)
+ return 0;
+
+ /* channel assignment (CEA-861-D Table 20) */
+ io_write(sd, REG_AUDIO_PATH, channel_assignment);
+
+ /* Audio output configuration */
+ reg = 0;
+ switch (pdata->audout_format) {
+ case AUDFMT_TYPE_I2S:
+ reg |= AUDCFG_BUS_I2S << AUDCFG_BUS_SHIFT;
+ break;
+ case AUDFMT_TYPE_SPDIF:
+ reg |= AUDCFG_BUS_SPDIF << AUDCFG_BUS_SHIFT;
+ break;
+ }
+ switch (state->audio_type) {
+ case AUDCFG_TYPE_PCM:
+ reg |= AUDCFG_TYPE_PCM << AUDCFG_TYPE_SHIFT;
+ break;
+ case AUDCFG_TYPE_OBA:
+ reg |= AUDCFG_TYPE_OBA << AUDCFG_TYPE_SHIFT;
+ break;
+ case AUDCFG_TYPE_DST:
+ reg |= AUDCFG_TYPE_DST << AUDCFG_TYPE_SHIFT;
+ sp_used_by_fifo = 0;
+ break;
+ case AUDCFG_TYPE_HBR:
+ reg |= AUDCFG_TYPE_HBR << AUDCFG_TYPE_SHIFT;
+ if (pdata->audout_layout == 1) {
+ /* demuxed via AP0:AP3 */
+ reg |= AUDCFG_HBR_DEMUX << AUDCFG_HBR_SHIFT;
+ if (pdata->audout_format == AUDFMT_TYPE_SPDIF)
+ sp_used_by_fifo = 0;
+ } else {
+ /* straight via AP0 */
+ reg |= AUDCFG_HBR_STRAIGHT << AUDCFG_HBR_SHIFT;
+ }
+ break;
+ }
+ if (pdata->audout_width == 32)
+ reg |= AUDCFG_I2SW_32 << AUDCFG_I2SW_SHIFT;
+ else
+ reg |= AUDCFG_I2SW_16 << AUDCFG_I2SW_SHIFT;
+
+ /* automatic hardware mute */
+ if (pdata->audio_auto_mute)
+ reg |= AUDCFG_AUTO_MUTE_EN;
+ /* clock polarity */
+ if (pdata->audout_invert_clk)
+ reg |= AUDCFG_CLK_INVERT;
+ io_write(sd, REG_AUDCFG, reg);
+
+ /* audio layout */
+ reg = (pdata->audout_layout) ? AUDIO_LAYOUT_LAYOUT1 : 0;
+ if (!pdata->audout_layoutauto)
+ reg |= AUDIO_LAYOUT_MANUAL;
+ if (sp_used_by_fifo)
+ reg |= AUDIO_LAYOUT_SP_FLAG;
+ io_write(sd, REG_AUDIO_LAYOUT, reg);
+
+ /* FIFO Latency value */
+ io_write(sd, REG_FIFO_LATENCY_VAL, 0x80);
+
+ /* Audio output port config */
+ if (sp_used_by_fifo) {
+ reg = AUDIO_OUT_ENABLE_AP0;
+ if (channel_assignment >= 0x01)
+ reg |= AUDIO_OUT_ENABLE_AP1;
+ if (channel_assignment >= 0x04)
+ reg |= AUDIO_OUT_ENABLE_AP2;
+ if (channel_assignment >= 0x0c)
+ reg |= AUDIO_OUT_ENABLE_AP3;
+ /* specific cases where AP1 is not used */
+ if ((channel_assignment == 0x04)
+ || (channel_assignment == 0x08)
+ || (channel_assignment == 0x0c)
+ || (channel_assignment == 0x10)
+ || (channel_assignment == 0x14)
+ || (channel_assignment == 0x18)
+ || (channel_assignment == 0x1c))
+ reg &= ~AUDIO_OUT_ENABLE_AP1;
+ /* specific cases where AP2 is not used */
+ if ((channel_assignment >= 0x14)
+ && (channel_assignment <= 0x17))
+ reg &= ~AUDIO_OUT_ENABLE_AP2;
+ } else {
+ reg = AUDIO_OUT_ENABLE_AP3 |
+ AUDIO_OUT_ENABLE_AP2 |
+ AUDIO_OUT_ENABLE_AP1 |
+ AUDIO_OUT_ENABLE_AP0;
+ }
+ if (pdata->audout_format == AUDFMT_TYPE_I2S)
+ reg |= (AUDIO_OUT_ENABLE_ACLK | AUDIO_OUT_ENABLE_WS);
+ io_write(sd, REG_AUDIO_OUT_ENABLE, reg);
+
+ /* reset test mode to normal audio freq auto selection */
+ io_write(sd, REG_TEST_MODE, 0x00);
+
+ return 0;
+}
+
+/* Soft Reset of specific hdmi info */
+static int
+tda1997x_hdmi_info_reset(struct v4l2_subdev *sd, u8 info_rst, bool reset_sus)
+{
+ u8 reg;
+
+ /* reset infoframe engine packets */
+ reg = io_read(sd, REG_HDMI_INFO_RST);
+ io_write(sd, REG_HDMI_INFO_RST, info_rst);
+
+ /* if infoframe engine has been reset clear INT_FLG_MODE */
+ if (reg & RESET_IF) {
+ reg = io_read(sd, REG_INT_FLG_CLR_MODE);
+ io_write(sd, REG_INT_FLG_CLR_MODE, reg);
+ }
+
+ /* Disable REFTIM to restart start-up-sequencer (SUS) */
+ reg = io_read(sd, REG_RATE_CTRL);
+ reg &= ~RATE_REFTIM_ENABLE;
+ if (!reset_sus)
+ reg |= RATE_REFTIM_ENABLE;
+ reg = io_write(sd, REG_RATE_CTRL, reg);
+
+ return 0;
+}
+
+static void
+tda1997x_power_mode(struct tda1997x_state *state, bool enable)
+{
+ struct v4l2_subdev *sd = &state->sd;
+ u8 reg;
+
+ if (enable) {
+ /* Automatic control of TMDS */
+ io_write(sd, REG_PON_OVR_EN, PON_DIS);
+ /* Enable current bias unit */
+ io_write(sd, REG_CFG1, PON_EN);
+ /* Enable deep color PLL */
+ io_write(sd, REG_DEEP_PLL7_BYP, PON_DIS);
+ /* Output buffers active */
+ reg = io_read(sd, REG_OF);
+ reg &= ~OF_VP_ENABLE;
+ io_write(sd, REG_OF, reg);
+ } else {
+ /* Power down EDID mode sequence */
+ /* Output buffers in HiZ */
+ reg = io_read(sd, REG_OF);
+ reg |= OF_VP_ENABLE;
+ io_write(sd, REG_OF, reg);
+ /* Disable deep color PLL */
+ io_write(sd, REG_DEEP_PLL7_BYP, PON_EN);
+ /* Disable current bias unit */
+ io_write(sd, REG_CFG1, PON_DIS);
+ /* Manual control of TMDS */
+ io_write(sd, REG_PON_OVR_EN, PON_EN);
+ }
+}
+
+static bool
+tda1997x_detect_tx_5v(struct v4l2_subdev *sd)
+{
+ u8 reg = io_read(sd, REG_DETECT_5V);
+
+ return ((reg & DETECT_5V_SEL) ? 1 : 0);
+}
+
+static bool
+tda1997x_detect_tx_hpd(struct v4l2_subdev *sd)
+{
+ u8 reg = io_read(sd, REG_DETECT_5V);
+
+ return ((reg & DETECT_HPD) ? 1 : 0);
+}
+
+static int
+tda1997x_detect_std(struct tda1997x_state *state,
+ struct v4l2_dv_timings *timings)
+{
+ struct v4l2_subdev *sd = &state->sd;
+ u32 vper;
+ u16 hper;
+ u16 hsper;
+ int i;
+
+ /*
+ * Read the FMT registers
+ * REG_V_PER: Period of a frame (or two fields) in MCLK(27MHz) cycles
+ * REG_H_PER: Period of a line in MCLK(27MHz) cycles
+ * REG_HS_WIDTH: Period of horiz sync pulse in MCLK(27MHz) cycles
+ */
+ vper = io_read24(sd, REG_V_PER) & MASK_VPER;
+ hper = io_read16(sd, REG_H_PER) & MASK_HPER;
+ hsper = io_read16(sd, REG_HS_WIDTH) & MASK_HSWIDTH;
+ v4l2_dbg(1, debug, sd, "Signal Timings: %u/%u/%u\n", vper, hper, hsper);
+ if (!vper || !hper || !hsper)
+ return -ENOLINK;
+
+ for (i = 0; v4l2_dv_timings_presets[i].bt.width; i++) {
+ const struct v4l2_bt_timings *bt;
+ u32 lines, width, _hper, _hsper;
+ u32 vmin, vmax, hmin, hmax, hsmin, hsmax;
+ bool vmatch, hmatch, hsmatch;
+
+ bt = &v4l2_dv_timings_presets[i].bt;
+ width = V4L2_DV_BT_FRAME_WIDTH(bt);
+ lines = V4L2_DV_BT_FRAME_HEIGHT(bt);
+ _hper = (u32)bt->pixelclock / width;
+ if (bt->interlaced)
+ lines /= 2;
+ /* vper +/- 0.7% */
+ vmin = ((27000000 / 1000) * 993) / _hper * lines;
+ vmax = ((27000000 / 1000) * 1007) / _hper * lines;
+ /* hper +/- 1.0% */
+ hmin = ((27000000 / 100) * 99) / _hper;
+ hmax = ((27000000 / 100) * 101) / _hper;
+ /* hsper +/- 2 (take care to avoid 32bit overflow) */
+ _hsper = 27000 * bt->hsync / ((u32)bt->pixelclock/1000);
+ hsmin = _hsper - 2;
+ hsmax = _hsper + 2;
+
+ /* vmatch matches the framerate */
+ vmatch = ((vper <= vmax) && (vper >= vmin)) ? 1 : 0;
+ /* hmatch matches the width */
+ hmatch = ((hper <= hmax) && (hper >= hmin)) ? 1 : 0;
+ /* hsmatch matches the hswidth */
+ hsmatch = ((hsper <= hsmax) && (hsper >= hsmin)) ? 1 : 0;
+ if (hmatch && vmatch && hsmatch) {
+ v4l2_print_dv_timings(sd->name, "Detected format: ",
+ &v4l2_dv_timings_presets[i],
+ false);
+ if (timings)
+ *timings = v4l2_dv_timings_presets[i];
+ return 0;
+ }
+ }
+
+ v4l_err(state->client, "no resolution match for timings: %d/%d/%d\n",
+ vper, hper, hsper);
+ return -ERANGE;
+}
+
+/* some sort of errata workaround for chip revision 0 (N1) */
+static void tda1997x_reset_n1(struct tda1997x_state *state)
+{
+ struct v4l2_subdev *sd = &state->sd;
+ u8 reg;
+
+ /* clear HDMI mode flag in BCAPS */
+ io_write(sd, REG_CLK_CFG, CLK_CFG_SEL_ACLK_EN | CLK_CFG_SEL_ACLK);
+ io_write(sd, REG_PON_OVR_EN, PON_EN);
+ io_write(sd, REG_PON_CBIAS, PON_EN);
+ io_write(sd, REG_PON_PLL, PON_EN);
+
+ reg = io_read(sd, REG_MODE_REC_CFG1);
+ reg &= ~0x06;
+ reg |= 0x02;
+ io_write(sd, REG_MODE_REC_CFG1, reg);
+ io_write(sd, REG_CLK_CFG, CLK_CFG_DIS);
+ io_write(sd, REG_PON_OVR_EN, PON_DIS);
+ reg = io_read(sd, REG_MODE_REC_CFG1);
+ reg &= ~0x06;
+ io_write(sd, REG_MODE_REC_CFG1, reg);
+}
+
+/*
+ * Activity detection must only be notified when stable_clk_x AND active_x
+ * bits are set to 1. If only stable_clk_x bit is set to 1 but not
+ * active_x, it means that the TMDS clock is not in the defined range
+ * and activity detection must not be notified.
+ */
+sta