lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Date:   Fri, 19 Oct 2018 12:54:38 +0200
From:   Michael Grzeschik <m.grzeschik@...gutronix.de>
To:     linux-media@...r.kernel.org
Cc:     linux-kernel@...r.kernel.org, stoth@...nellabs.com,
        mchehab@...nel.org, davem@...emloft.net,
        laurent.pinchart@...asonboard.com, kernel@...gutronix.de
Subject: [PATCH 1/2] media: mst3367: add support for mstar mst3367 HDMI RX

From: Steven Toth <stoth@...nellabs.com>

This patch is based on the work of Steven Toth. He reverse engineered
the driver by tracing the windows driver.

https://github.com/stoth68000/hdcapm/

Signed-off-by: Steven Toth <stoth@...nellabs.com>
Signed-off-by: Michael Grzeschik <m.grzeschik@...gutronix.de>
---
 MAINTAINERS                 |    6 +
 drivers/media/i2c/Kconfig   |   10 +
 drivers/media/i2c/Makefile  |    1 +
 drivers/media/i2c/mst3367.c | 1104 +++++++++++++++++++++++++++++++++++
 include/media/i2c/mst3367.h |   29 +
 5 files changed, 1150 insertions(+)
 create mode 100644 drivers/media/i2c/mst3367.c
 create mode 100644 include/media/i2c/mst3367.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 556f902b3766..9c69b7f9b2f9 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -9787,6 +9787,12 @@ L:	linux-mtd@...ts.infradead.org
 S:	Maintained
 F:	drivers/mtd/devices/docg3*
 
+MT9M032 APTINA SENSOR DRIVER
+M:	Michael Grzeschik <m.grzeschik@...gutronix.de>
+S:	Maintained
+F:	drivers/media/i2c/mst3367.c
+F:	include/media/i2c/mst3367.h
+
 MT9M032 APTINA SENSOR DRIVER
 M:	Laurent Pinchart <laurent.pinchart@...asonboard.com>
 L:	linux-media@...r.kernel.org
diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
index 82af97430e5b..3de53a09d88a 100644
--- a/drivers/media/i2c/Kconfig
+++ b/drivers/media/i2c/Kconfig
@@ -94,6 +94,16 @@ config VIDEO_MSP3400
 	  To compile this driver as a module, choose M here: the
 	  module will be called msp3400.
 
+config VIDEO_MST3367
+	tristate "Mstar MST3367 video decoders"
+	depends on VIDEO_V4L2 && I2C
+	help
+	  Support for the MStar MST3367 HDMI RX / SOC. It is found on
+	  the usb2hdcapm hdmi framegrabber from startech.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called mst3367.
+
 config VIDEO_CS3308
 	tristate "Cirrus Logic CS3308 audio ADC"
 	depends on VIDEO_V4L2 && I2C
diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
index a94eb03d10d4..f3e7a35018f8 100644
--- a/drivers/media/i2c/Makefile
+++ b/drivers/media/i2c/Makefile
@@ -1,6 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0
 msp3400-objs	:=	msp3400-driver.o msp3400-kthreads.o
 obj-$(CONFIG_VIDEO_MSP3400) += msp3400.o
+obj-$(CONFIG_VIDEO_MST3367) += mst3367.o
 
 obj-$(CONFIG_VIDEO_SMIAPP)	+= smiapp/
 obj-$(CONFIG_VIDEO_ET8EK8)	+= et8ek8/
diff --git a/drivers/media/i2c/mst3367.c b/drivers/media/i2c/mst3367.c
new file mode 100644
index 000000000000..7e2f529d96b3
--- /dev/null
+++ b/drivers/media/i2c/mst3367.c
@@ -0,0 +1,1104 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Driver for the MSTAR 3367 HDMI Receiver
+ *
+ * Copyright (c) 2017 Steven Toth <stoth@...nellabs.com>
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/videodev2.h>
+#include <linux/workqueue.h>
+#include <linux/v4l2-dv-timings.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-dv-timings.h>
+#include <media/v4l2-ctrls.h>
+#include <media/i2c/mst3367.h>
+
+static int debug;
+module_param_named(debug, debug, int, 0644);
+MODULE_PARM_DESC(debug, "debug level [def: 0]");
+
+MODULE_DESCRIPTION("Driver for MST3367 HDMI receiver");
+MODULE_AUTHOR("Steven Toth <stoth@...nellabs.com>");
+MODULE_LICENSE("GPL");
+
+#define BANK0  0x00
+#define BANK1  0x01
+#define BANK2  0x02
+#define BANK3  0x03
+#define DUMP_SHADOWS 0
+#define DUMP_REGISTERS 0
+
+/*
+ * This is how the register map was modified by the windows
+ * driver during the i2c-trace-driver-init-with-1080-signal.csv
+ * trace.
+ *
+ * BANK0 0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F
+ *      -----------------------------------------------
+ * 00 : 00
+ * 10 :                                           11 01
+ * 20 :
+ * 30 :
+ * 40 :    6F
+ * 50 :    89       20
+ * 60 :
+ * 70 :          90
+ * 80 :
+ * 90 : 15 15 62 10 00 00 00 00 00 00 00 10 00 00 00 00
+ * A0 : 00 00 00 10 00 20 00 00 01 20 01 15 95 05 04
+ * B0 : 20 E0 08 00 54 0C    00 00
+ * C0 :
+ * D0 :
+ * E0 :       00
+ * F0 :
+ *
+ * BANK1 0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F
+ *      -----------------------------------------------
+ * 00 : 01                                           02
+ * 10 :                   30 00 00 00 50
+ * 20 :             40                07
+ * 30 : 80 00 00
+ * 40 :
+ * 50 :
+ * 60 :
+ * 70 :
+ * 80 :
+ * 90 :
+ * A0 :
+ * B0 :
+ * C0 :
+ * D0 :
+ * E0 :
+ * F0 :
+ *
+ * BANK2 0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F
+ *      -----------------------------------------------
+ * 00 : 02 61 F5 02 01 00 08 04 03 28
+ * 10 :                      C0    FF FF FC 1A 00 00 00
+ * 20 : 00 00 26       A2    00                   A1
+ * 30 :
+ * 40 :
+ * 50 :
+ * 60 :
+ * 70 :
+ * 80 :
+ * 90 :
+ * A0 :
+ * B0 :
+ * C0 :
+ * D0 :
+ * E0 :
+ * F0 :
+ *
+ */
+
+struct mst3367_video_standards_s {
+	struct v4l2_dv_timings timings;
+	u32 htotal_min;
+	u32 htotal_max;
+	u32 vtotal_min;
+	u32 vtotal_max;
+	u32 hperiod_min;
+	u32 hperiod_max;
+	u32 vperiod_min;
+	u32 vperiod_max;
+	u32 interleaved;
+	u32 encoded_fps;
+	u32 hdmi_fpsX100;
+};
+
+struct mst3367_state {
+	struct v4l2_subdev sd;
+	struct v4l2_ctrl_handler hdl;
+
+	/* Is the mst3367 powered on? */
+	bool power_on;
+	bool haveSource;
+
+	/* controls */
+	struct v4l2_ctrl *hotplug_ctrl;
+	struct v4l2_ctrl *rx_sense_ctrl;
+
+	/* i2c */
+	struct i2c_adapter *i2c;
+	u8 i2c_addr;
+	u8 current_bank;
+
+	/* Detection */
+	const struct mst3367_video_standards_s *detected_standard;
+	int detected_signal;
+	struct {
+		u32 htotal;
+		u32 vtotal;
+		u32 hperiod;
+		u32 vperiod;
+		u32 detectdelay;
+		u32 hactive;
+		u32 interleaved;
+	} current_timings;
+
+	/* Shadow regs for monitoring writes. */
+	u8 regs[4][256];
+	u8 regs_updated[4][256];
+
+	u8 regb1r01_cached;
+	u8 regb2r48_cached;
+};
+
+static const struct mst3367_video_standards_s mst3367_video_standards[] = {
+	{V4L2_DV_BT_CEA_720X480P59_94, 845, 865, 520, 525, 310, 320, 595, 605,
+	 0, 60, 5994,},
+
+	// 720p30 - frontend doesn't reliably lock.
+	{V4L2_DV_BT_CEA_1280X720P30, 2300, 2500, 745, 755, 215, 235, 290, 310,
+	 0, 30, 3000,},
+	{V4L2_DV_BT_CEA_1280X720P50, 2965, 2985, 745, 755, 360, 380, 480, 520,
+	 0, 50, 5000,},
+	{V4L2_DV_BT_CEA_1280X720P60, 2470, 2480, 745, 755, 445, 455, 595, 605,
+	 0, 60, 6000,},
+
+	// Tivo
+	{V4L2_DV_BT_CEA_1280X720P60, 1645, 1655, 745, 755, 445, 455, 595, 605,
+	 0, 60, 6000,},
+
+	{V4L2_DV_BT_CEA_1920X1080P24, 4080, 4105, 1120, 1130, 260, 280, 230,
+	 250, 0, 24, 2400,},
+	{V4L2_DV_BT_CEA_1920X1080P25, 3950, 3970, 1120, 1130, 270, 290, 240,
+	 254, 0, 25, 2500,},
+	{V4L2_DV_BT_CEA_1920X1080P30, 2295, 3305, 1120, 1130, 330, 345, 290,
+	 310, 0, 30, 3000,},
+	{V4L2_DV_BT_CEA_1920X1080P50, 3950, 3970, 1120, 1130, 550, 570, 480,
+	 520, 0, 25, 5000,},
+	{V4L2_DV_BT_CEA_1920X1080P60, 3290, 3310, 1120, 1130, 665, 685, 595,
+	 605, 0, 30, 6000,},
+};
+
+static const struct mst3367_video_standards_s *find_video_standard(u32 htotal,
+								   u32 vtotal,
+								   u32 hperiod,
+								   u32 vperiod,
+								   u32
+								   interleaved)
+{
+	const struct mst3367_video_standards_s *e, *r = NULL;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(mst3367_video_standards); i++) {
+		e = &mst3367_video_standards[i];
+
+		if (htotal < e->htotal_min || htotal > e->htotal_max)
+			continue;
+		if (vtotal < e->vtotal_min || vtotal > e->vtotal_max)
+			continue;
+		if (hperiod < e->hperiod_min || hperiod > e->hperiod_max)
+			continue;
+		if (vperiod < e->vperiod_min || vperiod > e->vperiod_max)
+			continue;
+		if (interleaved != e->interleaved)
+			continue;
+
+		r = e;
+		break;
+	}
+
+	return r;
+}
+
+static inline struct mst3367_state *get_mst3367_state(struct v4l2_subdev *sd)
+{
+	return container_of(sd, struct mst3367_state, sd);
+}
+
+static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl)
+{
+	return &container_of(ctrl->handler, struct mst3367_state, hdl)->sd;
+}
+
+static void mst3367_notify_source_detect(struct v4l2_subdev *sd, int haveSource)
+{
+	struct v4l2_event ev;
+	struct mst3367_source_detect msd;
+
+	msd.present = haveSource;
+
+	/* sub-device events get pushed to the bridge via hdcapm_notify().
+	 * The bridge then forwards those events on to the v4l2_device,
+	 * and eventually they end up in userspace.
+	 */
+	v4l2_subdev_notify(sd, MST3367_SOURCE_DETECT, (void *)&msd);
+
+	memset(&ev, 0, sizeof(ev));
+	ev.type = V4L2_EVENT_SOURCE_CHANGE;
+	ev.u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION;
+	/* Input 0 - This event requires that the id matches the input index
+	 * (when used with a video device node)
+	 */
+	ev.id = 0;
+	v4l2_subdev_notify_event(sd, &ev);
+}
+
+/* The MST 3367 has multiple I2C register maps, banks 0-3, if the
+ * current bank doesn't match the requested bank, switch banks.
+ */
+static void mst3367_switch_bank(struct v4l2_subdev *sd, u8 bank)
+{
+	struct mst3367_state *state = get_mst3367_state(sd);
+	u8 buf[] = { 0x00, bank };
+
+	struct i2c_msg msg = {.addr = state->i2c_addr >> 1,
+		.flags = 0, .buf = buf, .len = 2
+	};
+
+	if (state->current_bank != bank) {
+		state->current_bank = bank;
+		if (i2c_transfer(state->i2c, &msg, 1) != 1)
+			v4l2_err(sd, "%s: switch bank error\n", __func__);
+	}
+}
+
+static u8 mst3367_rd(struct v4l2_subdev *sd, u8 bank, u8 reg)
+{
+	struct mst3367_state *state = get_mst3367_state(sd);
+	u8 b0 = reg;
+	u8 b1 = 0;
+
+	struct i2c_msg msg[] = {
+		{.addr = state->i2c_addr >> 1,
+		 .flags = 0, .buf = &b0, .len = 1},
+		{.addr = state->i2c_addr >> 1,
+		 .flags = I2C_M_RD, .buf = &b1, .len = 1}
+	};
+
+	mst3367_switch_bank(sd, bank);
+
+	if (i2c_transfer(state->i2c, msg, 2) != 2)
+		v4l2_err(sd, "%s: readreg error\n", __func__);
+
+	v4l2_dbg(2, debug, sd, "%s(bank=%d, reg=0x%02x) = 0x%02x\n",
+		 __func__, bank, reg, b1);
+
+	return b1;
+}
+
+static void mst3367_wr(struct v4l2_subdev *sd, u8 bank, u8 reg, u8 val)
+{
+	struct mst3367_state *state = get_mst3367_state(sd);
+	u8 buf[] = { reg, val };
+
+	struct i2c_msg msg = {.addr = state->i2c_addr >> 1, .flags = 0,
+		.buf = buf, .len = 2
+	};
+
+	v4l2_dbg(2, debug, sd, "%s(bank=%d, reg=0x%02x, value=0x%02x)\n",
+		 __func__, bank, reg, val);
+	mst3367_switch_bank(sd, bank);
+
+	state->regs[state->current_bank][reg] = val;
+	state->regs_updated[state->current_bank][reg] = 1;
+
+	if (i2c_transfer(state->i2c, &msg, 1) != 1)
+		v4l2_err(sd, "%s: writereg error\n", __func__);
+}
+
+static inline void mst3367_set(struct v4l2_subdev *sd, u8 bank, u8 reg, u8 mask)
+{
+	u8 val = mst3367_rd(sd, bank, reg);
+
+	val |= mask;
+	mst3367_wr(sd, bank, reg, val);
+}
+
+static inline void mst3367_clr(struct v4l2_subdev *sd, u8 bank, u8 reg, u8 mask)
+{
+	u8 val = mst3367_rd(sd, bank, reg);
+
+	val &= ~mask;
+	mst3367_wr(sd, bank, reg, val);
+}
+
+enum hpt_e {
+	RX_TMDS_HPD_OFF = 0x00,
+	RX_TMDS_A_HPD_ON = 0x01,
+	RX_TMDS_A_LINK_ON = 0x02,
+	RX_TMDS_B_HPD_ON = 0x10,
+	RX_TMDS_B_LINK_ON = 0x20,
+};
+
+static inline void MST3367_TMDS_HOT_PLUG(struct v4l2_subdev *sd, enum hpt_e e)
+{
+	u8 v = mst3367_rd(sd, BANK0, 0xB7);
+
+	v |= 0x02;
+
+	if (e & RX_TMDS_A_LINK_ON)
+		v &= ~0x02;
+	if (e & RX_TMDS_B_LINK_ON)
+		v &= ~0x02;
+
+	mst3367_wr(sd, BANK0, 0xB7, v);
+	msleep(20);
+}
+
+static inline void MST3367_HDMI_INIT(struct v4l2_subdev *sd)
+{
+	/* RxHdmiInit */
+	mst3367_clr(sd, BANK2, 0x01, 0xf0);
+	mst3367_set(sd, BANK2, 0x01, 0x40 | 0x20);
+	mst3367_set(sd, BANK2, 0x04, 0x01);
+	mst3367_wr(sd, BANK2, 0x06, 0x08);
+	mst3367_set(sd, BANK2, 0x09, 0x20);
+	mst3367_clr(sd, BANK0, 0x54, 0x10);
+	mst3367_set(sd, BANK0, 0xac, 0x80);
+
+	mst3367_set(sd, BANK0, 0x00, 0x80);
+	mst3367_set(sd, BANK0, 0xce, 0x80);
+	mst3367_clr(sd, BANK0, 0xcf, 0x07);
+	mst3367_set(sd, BANK0, 0xcf, 0x02);
+	mst3367_clr(sd, BANK0, 0x00, 0x80);
+}
+
+static inline void MST3367_HDCP_RESET(struct v4l2_subdev *sd)
+{
+	mst3367_wr(sd, BANK0, 0xb8, 0x10);	/* HDCP RESET */
+	mst3367_wr(sd, BANK0, 0xb8, 0x00);
+	msleep(20);
+}
+
+static inline void MST3367_HDMI_RESET(struct v4l2_subdev *sd)
+{
+	mst3367_wr(sd, BANK2, 0x07, 0xf4);
+	mst3367_wr(sd, BANK2, 0x07, 0x04);
+	msleep(20);
+}
+
+#if DUMP_SHADOWS
+static void dump_shadows(struct v4l2_subdev *sd, int bank)
+{
+	struct mst3367_state *state = get_mst3367_state(sd);
+	int i, j;
+	u8 line[80];
+
+	v4l2_info(sd, "B%d  0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F\n",
+		  bank);
+	v4l2_info(sd, "  -----------------------------------------------\n");
+	for (i = 0; i < 256; i += 16) {
+		sprintf(line, "%02X : ", i);
+		for (j = 0; j < 16; j++) {
+			if (state->regs_updated[bank][i + j])
+				sprintf(line + strlen(line), "%02X ",
+					state->regs[bank][i + j]);
+			else
+				sprintf(line + strlen(line), "   ");
+		}
+		sprintf(line + strlen(line), "\n");
+		v4l2_info(sd, line);
+	}
+}
+#endif
+
+#if DUMP_REGISTERS
+static void dump_registers(struct v4l2_subdev *sd, int bank)
+{
+	int i, j;
+	u8 line[80];
+	u8 vals[256];
+
+	for (i = 0; i < sizeof(vals); i++)
+		vals[i] = mst3367_rd(sd, bank, i);
+
+	v4l2_info(sd, "B%d  0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F\n",
+		  bank);
+	v4l2_info(sd, "   -----------------------------------------------\n");
+	for (i = 0; i < 256; i += 16) {
+		sprintf(line, "%02X : ", i);
+		for (j = 0; j < 16; j++)
+			sprintf(line + strlen(line), "%02X ", vals[i + j]);
+		sprintf(line + strlen(line), "\n");
+		v4l2_info(sd, line);
+	}
+}
+#endif
+
+static int MST3367_HDMI_MODE_DETECT(struct v4l2_subdev *sd, int *locked)
+{
+	struct mst3367_state *state = get_mst3367_state(sd);
+	int ret = -ENOLINK;
+	u8 r[0xff];
+	u16 t;
+
+	*locked = 0;
+
+	/* Do we have a signal detect / lock? */
+	if (mst3367_rd(sd, BANK0, 0x55) & 0x3c) {
+		/* We have a signal, extract timing data. */
+		state->current_timings.htotal = (mst3367_rd(sd,
+							    BANK0, 0x6a) << 8
+						| mst3367_rd(sd, BANK0, 0x6b))
+		    & 0xfff;
+		state->current_timings.vtotal = (mst3367_rd(sd,
+							    BANK0, 0x5b) << 8
+						| mst3367_rd(sd, BANK0, 0x5c))
+		    & 0x7ff;
+		state->current_timings.hactive =
+		    (mst3367_rd(sd, BANK2, 0x29) << 8 |
+		     mst3367_rd(sd, BANK2, 0x28))
+		    & 0x1fff;
+
+		r[0x57] = mst3367_rd(sd, BANK0, 0x57) & 0x3f;
+		r[0x58] = mst3367_rd(sd, BANK0, 0x58);
+		r[0x59] = mst3367_rd(sd, BANK0, 0x59) & 0x3f;
+		r[0x5a] = mst3367_rd(sd, BANK0, 0x5a);
+		r[0x5f] = mst3367_rd(sd, BANK0, 0x5f) & 0x02;
+
+		t = ((r[0x57] << 8) | r[0x58]);
+		if (t > 0)
+			state->current_timings.hperiod =
+			    ((1600000) / ((r[0x57] << 8) | (r[0x58] << 0)));
+
+		t = ((r[0x59] << 8) | r[0x5a]);
+		if (t > 0)
+			state->current_timings.vperiod =
+			    ((1250000) / ((r[0x59] << 8) | (r[0x5a] << 0)));
+
+		state->current_timings.interleaved = r[0x5f] >> 1;
+
+		state->current_timings.detectdelay = ((r[0x59] << 8) | r[0x5a]);
+		state->current_timings.detectdelay =
+		    ((state->current_timings.detectdelay + 63) * 2) / 125;
+
+		v4l2_dbg(2, debug, sd,
+			 "%s() htotal = %d, vtotal = %d, hperiod = %d, vperiod = %d, detectdelay = %d, hactive = %d, interleaved = %d\n",
+			 __func__,
+			 state->current_timings.htotal,
+			 state->current_timings.vtotal,
+			 state->current_timings.hperiod,
+			 state->current_timings.vperiod,
+			 state->current_timings.detectdelay,
+			 state->current_timings.hactive,
+			 state->current_timings.interleaved);
+
+		/* Looking the signal format. If its somet hing we
+		 * support then return lock, else no lock.
+		 */
+		state->detected_standard =
+		    find_video_standard(state->current_timings.htotal,
+					state->current_timings.vtotal,
+					state->current_timings.hperiod,
+					state->current_timings.vperiod,
+					state->current_timings.interleaved);
+		if (state->detected_standard) {
+			*locked = 1;
+		} else {
+			/* Detected a signal on the wire, but we have no
+			 * standard defined for it.
+			 */
+			v4l2_dbg(2, debug, sd,
+				 "%s() No detected standard for htotal = %d, vtotal = %d, hperiod = %d, vperiod = %d, detectdelay = %d, hactive = %d, interleaved = %d\n",
+				 __func__,
+				 state->current_timings.htotal,
+				 state->current_timings.vtotal,
+				 state->current_timings.hperiod,
+				 state->current_timings.vperiod,
+				 state->current_timings.detectdelay,
+				 state->current_timings.hactive,
+				 state->current_timings.interleaved);
+		}
+
+		ret = 0;
+
+		/*
+		 * printk(KERN_ERR "%s() r01 = 0x%x\n", __func__, r[0x01]);
+		 * HDMI_MD 2
+		 * HDCP_OP_STS 1
+		 * HDCP_MD 0
+		 * 0x0: DVI, without HDCP.
+		 * 001: DVI OESS* + HDCP, without advance cipher.
+		 * 011: DVI EESS** + HDCP, with advance cipher.
+		 * 1x0: HDMI EESS, without HDCP.
+		 * 101: HDMI EESS + HDCP, without advance cipher.
+		 * 111: HDMI + HDCP EESS, with advance cipher.
+		 * *OESS: Original Encryption Status Signaling.
+		 * **EESS: Enhanced Encryption Status Signaling
+		 */
+	}
+
+	state->regb1r01_cached = mst3367_rd(sd, BANK1, 0x01);
+	state->regb2r48_cached = mst3367_rd(sd, BANK2, 0x48);
+
+	if (*locked) {
+		state->detected_signal = 1;
+		if (!state->haveSource) {
+			state->haveSource = 1;
+			mst3367_notify_source_detect(sd, state->haveSource);
+		}
+	} else {
+		memset(&state->current_timings, 0,
+		       sizeof(state->current_timings));
+		state->detected_signal = 0;
+		if (state->haveSource) {
+			state->haveSource = 0;
+			mst3367_notify_source_detect(sd, state->haveSource);
+		}
+	}
+
+	return ret;
+}
+
+static inline u32 MST3367_HdmiGetPacketStatus(struct v4l2_subdev *sd)
+{
+	u32 status = 0;
+	u8 r0b, r0c, r0e;
+
+	r0b = mst3367_rd(sd, BANK2, 0x0b) & 0xff;
+	r0c = mst3367_rd(sd, BANK2, 0x0c) & 0x3f;
+	r0e = mst3367_rd(sd, BANK2, 0x0e) & 0x08;
+
+	status = (r0c << 8) | r0b;
+	if (r0e & 0x08)
+		status |= 0x8000;
+
+	v4l2_dbg(1, debug, sd, "%s() status = 0x%08x\n", __func__, status);
+
+	return status;
+}
+
+static inline u32 MST3367_HdmiGetPacketColor(struct v4l2_subdev *sd)
+{
+	u32 color = 0;
+
+	u8 r48 = mst3367_rd(sd, BANK2, 0x48) & 0x60;
+
+	if (r48 == 0x00)
+		color = 0;	/* RX_INPUT_RGB */
+	else if (r48 == 0x20)
+		color = 1;	/* RX_INPUT_YUV422 */
+	else if (r48 == 0x40)
+		color = 2;	/* RX_INPUT_YUV444 */
+
+	return color;
+}
+
+static int mst3367_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct v4l2_subdev *sd = to_sd(ctrl);
+
+	v4l2_dbg(1, debug, sd, "%s: ctrl id: %d, ctrl->val %d\n", __func__,
+		 ctrl->id, ctrl->val);
+
+	return -EINVAL;
+}
+
+static const struct v4l2_ctrl_ops mst3367_ctrl_ops = {
+	.s_ctrl = mst3367_s_ctrl,
+};
+
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+/* Register bits 15-8 represent bank, bits 7-0 register. */
+static int mst3367_g_register(struct v4l2_subdev *sd,
+			      struct v4l2_dbg_register *reg)
+{
+	reg->val = mst3367_rd(sd, (reg->reg >> 8) & 0xff, reg->reg & 0xff);
+	reg->size = 1;
+	return 0;
+}
+
+static int mst3367_s_register(struct v4l2_subdev *sd,
+			      const struct v4l2_dbg_register *reg)
+{
+	mst3367_wr(sd, (reg->reg >> 8) & 0xff, reg->reg & 0xff,
+		   reg->val & 0xff);
+	return 0;
+}
+#endif
+
+static int mst3367_log_status(struct v4l2_subdev *sd)
+{
+	struct mst3367_state *state = get_mst3367_state(sd);
+
+	v4l2_info(sd, "source_connected:  %s\n",
+		  state->haveSource ? "yes" : "no");
+	v4l2_info(sd, "signal_detected:   %s\n",
+		  state->detected_signal ? "yes" : "no");
+	v4l2_info(sd, "power:             %s\n",
+		  state->power_on ? "on" : "off");
+
+	if (state->detected_signal) {
+		v4l2_info(sd, "standard:          %dx%dx%d%s\n",
+			  state->detected_standard->timings.bt.width,
+			  state->detected_standard->timings.bt.height,
+			  state->detected_standard->hdmi_fpsX100 / 100,
+			  state->detected_standard->timings.bt.interlaced
+			  ? "i" : "p");
+	} else {
+		v4l2_info(sd, "standard:          n/a\n");
+	}
+
+	v4l2_info(sd,
+		  "htotal:            %d (horizontal front porch, sync, back porch + active pixels)\n",
+		  state->current_timings.htotal);
+	v4l2_info(sd,
+		  "vtotal:            %d (vertical front porch, sync, back porch + active pixels)\n",
+		  state->current_timings.vtotal);
+	v4l2_info(sd, "hperiod:           %d (%d.%d KHz)\n",
+		  state->current_timings.hperiod,
+		  state->current_timings.hperiod / 10,
+		  state->current_timings.hperiod % 10);
+
+	v4l2_info(sd, "vperiod:           %d (%d.%d Hz)\n",
+		  state->current_timings.vperiod,
+		  state->current_timings.vperiod / 10,
+		  state->current_timings.vperiod % 10);
+
+	v4l2_info(sd, "detectdelay:       %d\n",
+		  state->current_timings.detectdelay);
+	v4l2_info(sd, "hactive:           %d\n",
+		  state->current_timings.hactive);
+	v4l2_info(sd, "scanline:          %s\n",
+		  state->current_timings.interleaved
+		  ? "interleaved" : "progressive");
+
+	v4l2_info(sd, "input colorspace:  %s\n",
+		  (state->regb2r48_cached & 0x60) == 0x00 ? "RX_INPUT_RGB" :
+		  (state->regb2r48_cached & 0x60) == 0x20 ? "RX_INPUT_YUV422" :
+		  (state->regb2r48_cached & 0x60) ==
+		  0x40 ? "RX_INPUT_YUV444" : "UNDEFINED");
+
+	v4l2_info(sd, "1.01:              0x%02x\n", state->regb1r01_cached);
+	v4l2_info(sd, "1.01.b2:           %s\n",
+		  state->regb1r01_cached & 0x04 ? "HDMI" : "DVI");
+	v4l2_info(sd, "1.01.b0:           %s\n",
+		  state->regb1r01_cached & 0x01
+		  ? "HDCP active" : "HDCP not present");
+
+	return 0;
+}
+
+static void mst3367_init_setup(struct v4l2_subdev *sd)
+{
+	int i;
+	u8 csctbl[] = {
+		0x40,
+		0x08, 0x02, 0x03, 0x65, 0x7E, 0x28,	/* M11, M12, M13 */
+		0x78, 0xB9, 0x0B, 0x65, 0x79, 0xD6,	/* M21, M22, M23 */
+		0x7F, 0x45, 0x01, 0x27, 0x08, 0x02,	/* M31, M32, M33 */
+		0x20, 0x00, 0x02, 0x81, 0x20, 0x01,	/*  A1,  A2,  A3 */
+		0x15, 0x95, 0x05, 0x20, 0xC0, 0x08
+	};
+
+	v4l2_dbg(1, debug, sd, "%s\n", __func__);
+
+	MST3367_TMDS_HOT_PLUG(sd, RX_TMDS_HPD_OFF);
+
+	/* RxGeneralInit */
+	mst3367_wr(sd, BANK0, 0x41, 0x6f);
+	mst3367_wr(sd, BANK0, 0xb8, 0x00);
+
+	/* RxTmdsInit */
+	mst3367_wr(sd, BANK1, 0x0f, 0x02);
+	mst3367_wr(sd, BANK1, 0x16, 0x30);
+	mst3367_wr(sd, BANK1, 0x17, 0x00);
+	mst3367_wr(sd, BANK1, 0x18, 0x00);
+	mst3367_wr(sd, BANK1, 0x19, 0x00);
+	mst3367_wr(sd, BANK1, 0x1a, 0x50);
+	mst3367_clr(sd, BANK1, 0x2a, 0x07);
+	mst3367_set(sd, BANK1, 0x2a, 0x07);
+	mst3367_wr(sd, BANK2, 0x08, 0x03);
+
+	/* RxHdcpInit */
+	/* receive HDCP */
+	mst3367_wr(sd, BANK1, 0x24, 0x40);
+
+	mst3367_wr(sd, BANK1, 0x30, 0x80);
+	mst3367_wr(sd, BANK1, 0x31, 0x00);
+	mst3367_wr(sd, BANK1, 0x32, 0x00);
+
+	/* RxVideoInit */
+	mst3367_wr(sd, BANK0, 0xb0, 0x14);
+	mst3367_set(sd, BANK0, 0xae, 0x04);
+	mst3367_wr(sd, BANK0, 0xad, 0x05);	/* ENABLE LOW.PASS FILTER */
+	mst3367_wr(sd, BANK0, 0xb1, 0xe0);	/* From windows i2c trace. */
+	mst3367_wr(sd, BANK0, 0xb2, 0x08);	/* From windows i2c trace. */
+	mst3367_wr(sd, BANK0, 0xb3, 0x00);
+	mst3367_wr(sd, BANK0, 0xb4, 0x55);
+
+	/* RxAudioInit */
+	mst3367_clr(sd, BANK0, 0xb4, 0x03);
+	mst3367_wr(sd, BANK2, 0x01, 0x61);
+	mst3367_wr(sd, BANK2, 0x02, 0xf5);
+	mst3367_set(sd, BANK2, 0x03, 0x02);
+	mst3367_wr(sd, BANK2, 0x04, 0x01);
+	mst3367_wr(sd, BANK2, 0x05, 0x00);
+	mst3367_wr(sd, BANK2, 0x06, 0x08);
+	mst3367_wr(sd, BANK2, 0x1c, 0x1a);
+	mst3367_wr(sd, BANK2, 0x1d, 0x00);
+	mst3367_wr(sd, BANK2, 0x1e, 0x00);
+	mst3367_wr(sd, BANK2, 0x1f, 0x00);
+	mst3367_clr(sd, BANK2, 0x25, 0xa2);
+	mst3367_set(sd, BANK2, 0x25, 0xa2);
+
+	/* unknown */
+	mst3367_set(sd, BANK2, 0x02, 0x80);
+	mst3367_set(sd, BANK2, 0x07, 0x04);
+	mst3367_wr(sd, BANK2, 0x17, 0xc0);
+	mst3367_wr(sd, BANK2, 0x19, 0xff);
+	mst3367_wr(sd, BANK2, 0x1a, 0xff);
+	mst3367_wr(sd, BANK2, 0x1b, 0xfc);
+	mst3367_wr(sd, BANK2, 0x20, 0x00);
+	mst3367_clr(sd, BANK2, 0x21, 0x03);
+	mst3367_wr(sd, BANK2, 0x22, 0x26);
+	mst3367_wr(sd, BANK2, 0x27, 0x00);
+	mst3367_set(sd, BANK2, 0x2e, 0xa1);
+
+	/* unknown */
+	mst3367_wr(sd, BANK0, 0xab, 0x15);	/* [COLOR.RANGE] 0x15 */
+	mst3367_clr(sd, BANK0, 0xac, 0x3f);
+	mst3367_set(sd, BANK0, 0xac, 0x15);
+
+	/* RxSwitchSource - HDMI */
+	MST3367_TMDS_HOT_PLUG(sd, RX_TMDS_HPD_OFF);
+	MST3367_HDCP_RESET(sd);
+	MST3367_HDMI_RESET(sd);
+	mst3367_wr(sd, BANK0, 0x51, 0x89);
+	MST3367_TMDS_HOT_PLUG(sd, RX_TMDS_A_HPD_ON | RX_TMDS_A_LINK_ON);
+	mst3367_wr(sd, BANK0, 0xB7, 0x00);
+
+	/* Patches */
+	mst3367_wr(sd, BANK0, 0xE2, 0x00);	/* DISABLE AUTO POSITION */
+	mst3367_wr(sd, BANK0, 0x1e, 0x11);
+	mst3367_wr(sd, BANK0, 0x1f, 0x01);
+	mst3367_wr(sd, BANK0, 0x73, 0x90);
+	mst3367_wr(sd, BANK0, 0xb5, 0x0c);
+
+	/* CSC */
+	mst3367_wr(sd, BANK0, 0x90, 0x15);	/* Color Range */
+	mst3367_wr(sd, BANK0, 0x91, 0x15);
+
+	for (i = 0; i < sizeof(csctbl); i++)
+		mst3367_wr(sd, BANK0, 0x92 + i, csctbl[i]);
+
+	/* RX_OUTPUT_YUV422 / 08.BITS / EXTERNAL SYNC */
+	mst3367_wr(sd, BANK0, 0xB0, 0x20);
+
+	MST3367_HDMI_INIT(sd);
+}
+
+static int mst3367_s_power(struct v4l2_subdev *sd, int on)
+{
+	struct mst3367_state *state = get_mst3367_state(sd);
+
+	v4l2_dbg(1, debug, sd, "%s: power %s\n", __func__, on ? "on" : "off");
+
+	/* TODO: Turn on/off the TMDS clocks. */
+
+	state->power_on = on;
+	if (on)
+		mst3367_init_setup(sd);
+
+	return true;
+}
+
+static int mst3367_isr(struct v4l2_subdev *sd, u32 status, bool *handled)
+{
+	v4l2_dbg(0, debug, sd, "%s()\n", __func__);
+	*handled = true;
+	return 0;
+}
+
+static int mst3367_subscribe_event(struct v4l2_subdev *sd, struct v4l2_fh *fh,
+				   struct v4l2_event_subscription *sub)
+{
+	switch (sub->type) {
+	case V4L2_EVENT_SOURCE_CHANGE:
+		return v4l2_src_change_event_subdev_subscribe(sd, fh, sub);
+	case V4L2_EVENT_CTRL:
+		return v4l2_ctrl_subdev_subscribe_event(sd, fh, sub);
+	default:
+		return -EINVAL;
+	}
+}
+
+static const struct v4l2_subdev_core_ops mst3367_core_ops = {
+	.log_status = mst3367_log_status,
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+	.g_register = mst3367_g_register,
+	.s_register = mst3367_s_register,
+#endif
+	.s_power = mst3367_s_power,
+	.interrupt_service_routine = mst3367_isr,
+	.subscribe_event = mst3367_subscribe_event,
+	.unsubscribe_event = v4l2_event_subdev_unsubscribe,
+};
+
+static int mst3367_s_stream(struct v4l2_subdev *sd, int enable)
+{
+	v4l2_dbg(1, debug, sd, "%s: %sable\n", __func__,
+		 (enable ? "en" : "dis"));
+
+	if (!enable)
+		mst3367_s_power(sd, 0);
+
+	return 0;
+}
+
+static const struct v4l2_dv_timings_cap mst3367_timings_cap = {
+	.type = V4L2_DV_BT_656_1120,
+	/* keep this initialization for compatibility with GCC < 4.4.6 */
+	.reserved = {0},
+	V4L2_INIT_BT_TIMINGS(0, 1920, 0, 1200, 25000000, 170000000,
+			     V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT |
+			     V4L2_DV_BT_STD_GTF | V4L2_DV_BT_STD_CVT,
+			     V4L2_DV_BT_CAP_PROGRESSIVE |
+			     V4L2_DV_BT_CAP_REDUCED_BLANKING |
+			     V4L2_DV_BT_CAP_CUSTOM)
+};
+
+static int mst3367_g_dv_timings(struct v4l2_subdev *sd,
+				struct v4l2_dv_timings *timings)
+{
+	struct mst3367_state *state = get_mst3367_state(sd);
+
+	v4l2_dbg(1, debug, sd, "%s:\n", __func__);
+
+	if (!state->detected_signal)
+		return -ENODATA;
+
+	*timings = state->detected_standard->timings;
+
+	return 0;
+}
+
+static int mst3367_enum_dv_timings(struct v4l2_subdev *sd,
+				   struct v4l2_enum_dv_timings *timings)
+{
+	return v4l2_enum_dv_timings_cap(timings, &mst3367_timings_cap, NULL,
+					NULL);
+}
+
+static int mst3367_dv_timings_cap(struct v4l2_subdev *sd,
+				  struct v4l2_dv_timings_cap *cap)
+{
+	if (cap->pad != 0)
+		return -EINVAL;
+
+	*cap = mst3367_timings_cap;
+
+	return 0;
+}
+
+static int mst3367_video_s_routing(struct v4l2_subdev *sd, u32 input,
+				   u32 output, u32 config)
+{
+	v4l2_dbg(1, debug, sd, "%s(input=%d, output=%d, config=0x%x)\n",
+		 __func__, input, output, config);
+
+	return 0;
+}
+
+static int mst3367_query_dv_timings(struct v4l2_subdev *sd,
+				    struct v4l2_dv_timings *timings)
+{
+	struct mst3367_state *state = get_mst3367_state(sd);
+	int locked;
+	int ret;
+#if DUMP_SHADOWS || DUMP_REGISTERS
+	static int count;
+#endif
+
+	v4l2_dbg(2, debug, sd, "%s()\n", __func__);
+
+	memset(timings, 0, sizeof(struct v4l2_dv_timings));
+
+	/* Perform video standard detection. */
+	ret = MST3367_HDMI_MODE_DETECT(sd, &locked);
+	if (ret < 0 || !locked) {
+		/* No timings could be detected because no signal was found. */
+		return ret;
+	}
+
+	/* We're detected a signal, return formal timing. */
+	*timings = state->detected_standard->timings;
+
+	if (debug > 1) {
+		v4l2_print_dv_timings(sd->name, "timings: ", timings, true);
+		MST3367_HdmiGetPacketColor(sd);
+	}
+#if DUMP_SHADOWS
+	if (count++ > 6) {
+		count = 0;
+		dump_shadows(sd, 0);
+		dump_shadows(sd, 1);
+		dump_shadows(sd, 2);
+	}
+#endif
+
+#if DUMP_REGISTERS
+	if (count++ > 10) {
+		count = 0;
+		dump_registers(sd, BANK0);
+		dump_registers(sd, BANK1);
+		dump_registers(sd, BANK2);
+	}
+#endif
+
+	return 0;		/* Success  - Signal locked */
+}
+
+static int mst3367_g_input_status(struct v4l2_subdev *sd, u32 *status)
+{
+	struct mst3367_state *state = get_mst3367_state(sd);
+
+	if (state->detected_signal) {
+		/* Clear these failed bits, we have a signal. */
+		*status &= ~V4L2_IN_ST_NO_POWER;
+		*status &= ~V4L2_IN_ST_NO_SIGNAL;
+	} else {
+		/* Establish failed bits. */
+		*status |= V4L2_IN_ST_NO_POWER;
+		*status |= V4L2_IN_ST_NO_SIGNAL;
+	}
+
+	return 0;
+}
+
+static const struct v4l2_subdev_video_ops mst3367_video_ops = {
+	.s_stream = mst3367_s_stream,
+	.g_dv_timings = mst3367_g_dv_timings,
+	.s_routing = mst3367_video_s_routing,
+	.query_dv_timings = mst3367_query_dv_timings,
+	.g_input_status = mst3367_g_input_status,
+};
+
+static const struct v4l2_subdev_pad_ops mst3367_pad_ops = {
+	.enum_dv_timings = mst3367_enum_dv_timings,
+	.dv_timings_cap = mst3367_dv_timings_cap,
+};
+
+static const struct v4l2_subdev_ops mst3367_ops = {
+	.core = &mst3367_core_ops,
+	.video = &mst3367_video_ops,
+	.pad = &mst3367_pad_ops,
+};
+
+static int mst3367_probe(struct i2c_client *client,
+			 const struct i2c_device_id *id)
+{
+	struct mst3367_state *state;
+	struct v4l2_ctrl_handler *hdl;
+	struct v4l2_subdev *sd;
+	int err = -EIO;
+
+	v4l_dbg(1, debug, client, "%s()\n", __func__);
+
+	/* Check if the adapter supports the needed features */
+	if (!i2c_check_functionality(client->adapter,
+				     I2C_FUNC_SMBUS_BYTE_DATA)) {
+		v4l_err(client, "%s() no dice!\n", __func__);
+		return -EIO;
+	}
+
+	v4l_dbg(1, debug, client, "detecting mst3367 client on address 0x%x\n",
+		client->addr << 1);
+
+	state = devm_kzalloc(&client->dev, sizeof(*state), GFP_KERNEL);
+	if (!state)
+		return -ENOMEM;
+
+	state->current_bank = 0xff;
+	state->i2c = client->adapter;
+	state->i2c_addr = 0x9c;
+
+	sd = &state->sd;
+	v4l2_i2c_subdev_init(sd, client, &mst3367_ops);
+	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+
+	hdl = &state->hdl;
+	v4l2_ctrl_handler_init(hdl, 2);
+
+	state->hotplug_ctrl =
+	    v4l2_ctrl_new_std(hdl, NULL, V4L2_CID_DV_TX_HOTPLUG, 0, 1, 0, 0);
+	state->rx_sense_ctrl =
+	    v4l2_ctrl_new_std(hdl, NULL, V4L2_CID_DV_TX_RXSENSE, 0, 1, 0, 0);
+	sd->ctrl_handler = hdl;
+	if (hdl->error) {
+		err = hdl->error;
+		goto err_hdl;
+	}
+
+	if (mst3367_rd(sd, BANK0, 0x50) != 1) {
+		v4l2_err(sd, "chip_revision != 1\n");
+		err = -EIO;
+		goto err_hdl;
+	}
+
+	state->detected_signal = 0;
+
+	mst3367_init_setup(sd);
+	v4l2_ctrl_handler_setup(&state->hdl);
+
+	v4l2_info(sd, "%s found and initialized @ addr 0x%x (%s)\n",
+		  client->name, client->addr << 1, client->adapter->name);
+
+	if (debug)
+		v4l2_info(sd, "Debugging is enabled\n");
+
+	v4l2_info(sd, "driver loaded\n");
+
+	return 0;
+
+err_hdl:
+	v4l2_ctrl_handler_free(&state->hdl);
+	return err;
+}
+
+static int mst3367_remove(struct i2c_client *client)
+{
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+
+	v4l2_dbg(1, debug, sd, "%s()\n", __func__);
+
+	v4l2_dbg(1, debug, sd, "%s removed @ 0x%x (%s)\n", client->name,
+		 client->addr << 1, client->adapter->name);
+
+	v4l2_device_unregister_subdev(sd);
+	v4l2_ctrl_handler_free(sd->ctrl_handler);
+
+	v4l_info(client, "driver unloaded\n");
+
+	return 0;
+}
+
+static struct i2c_device_id mst3367_id[] = {
+	{"mst3367", 0},
+	{}
+};
+
+MODULE_DEVICE_TABLE(i2c, mst3367_id);
+
+static struct i2c_driver mst3367_driver = {
+	.driver = {
+		   .name = "mst3367",
+		   },
+	.probe = mst3367_probe,
+	.remove = mst3367_remove,
+	.id_table = mst3367_id,
+};
+
+module_i2c_driver(mst3367_driver);
diff --git a/include/media/i2c/mst3367.h b/include/media/i2c/mst3367.h
new file mode 100644
index 000000000000..bdbe70258df3
--- /dev/null
+++ b/include/media/i2c/mst3367.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Driver for the MSTAR 3367 HDMI Receiver
+ *
+ * Copyright (c) 2017 Steven Toth <stoth@...nellabs.com>
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *
+ * GNU General Public License for more details.
+ */
+
+#ifndef MST3367_H
+#define MST3367_H
+
+/* notify events */
+#define MST3367_SOURCE_DETECT 0
+
+struct mst3367_source_detect {
+	int present;
+};
+
+#endif /* MST3367_H */
-- 
2.19.0

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ