From: Steven Toth <stoth@xxxxxxxxxxxxxx> 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@xxxxxxxxxxxxxx> Signed-off-by: Michael Grzeschik <m.grzeschik@xxxxxxxxxxxxxx> --- drivers/media/i2c/Kconfig | 10 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/mst3367.c | 1104 +++++++++++++++++++++++++++++++++++ include/media/i2c/mst3367.h | 29 + 4 files changed, 1144 insertions(+) create mode 100644 drivers/media/i2c/mst3367.c create mode 100644 include/media/i2c/mst3367.h diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 704af210e270..10dc0023494b 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 260d4d9ec2a1..fad65cce90f9 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@xxxxxxxxxxxxxx> + * + * 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@xxxxxxxxxxxxxx>"); +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@xxxxxxxxxxxxxx> + * + * 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.1