Message ID | 1507783506-3884-4-git-send-email-tharvey@gateworks.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Hi Tim, Here is my review of this v2: On 10/12/17 06:45, Tim Harvey wrote: > Add support for the TDA1997x HDMI receivers. > > Cc: Hans Verkuil <hverkuil@xs4all.nl> > Signed-off-by: Tim Harvey <tharvey@gateworks.com> > --- > v2: > - implement dv timings enum/cap > - remove deprecated g_mbus_config op > - fix dv_query_timings > - add EDID get/set handling > - remove max-pixel-rate support > - add audio codec DAI support > - use new audio bindings > > --- > drivers/media/i2c/Kconfig | 9 + > drivers/media/i2c/Makefile | 1 + > drivers/media/i2c/tda1997x.c | 3336 ++++++++++++++++++++++++++++++++++ > include/dt-bindings/media/tda1997x.h | 78 + > include/media/i2c/tda1997x.h | 53 + > 5 files changed, 3477 insertions(+) > create mode 100644 drivers/media/i2c/tda1997x.c > create mode 100644 include/dt-bindings/media/tda1997x.h > create mode 100644 include/media/i2c/tda1997x.h > > diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig > index 9415389..c2b0400 100644 > --- a/drivers/media/i2c/Kconfig > +++ b/drivers/media/i2c/Kconfig > @@ -56,6 +56,15 @@ config VIDEO_TDA9840 > To compile this driver as a module, choose M here: the > module will be called tda9840. > > +config VIDEO_TDA1997X > + tristate "NXP TDA1997x HDMI receiver" > + depends on VIDEO_V4L2 && I2C && VIDEO_V4L2_SUBDEV_API > + ---help--- > + V4L2 subdevice driver for the NXP TDA1997x HDMI receivers. > + > + To compile this driver as a module, choose M here: the > + module will be called tda1997x. > + > config VIDEO_TEA6415C > tristate "Philips TEA6415C audio processor" > depends on I2C > diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile > index c843c18..58f2b2e 100644 > --- a/drivers/media/i2c/Makefile > +++ b/drivers/media/i2c/Makefile > @@ -12,6 +12,7 @@ obj-$(CONFIG_VIDEO_TVAUDIO) += tvaudio.o > obj-$(CONFIG_VIDEO_TDA7432) += tda7432.o > obj-$(CONFIG_VIDEO_SAA6588) += saa6588.o > obj-$(CONFIG_VIDEO_TDA9840) += tda9840.o > +obj-$(CONFIG_VIDEO_TDA1997X) += tda1997x.o > obj-$(CONFIG_VIDEO_TEA6415C) += tea6415c.o > obj-$(CONFIG_VIDEO_TEA6420) += tea6420.o > obj-$(CONFIG_VIDEO_SAA7110) += saa7110.o > diff --git a/drivers/media/i2c/tda1997x.c b/drivers/media/i2c/tda1997x.c > new file mode 100644 > index 0000000..bf06684 > --- /dev/null > +++ b/drivers/media/i2c/tda1997x.c > @@ -0,0 +1,3336 @@ > +/* > + * Copyright (C) 2017 Gateworks Corporation > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License as published by > + * the Free Software Foundation; either version 2 of the License, or > + * (at your option) any later version. > + */ > +#include <linux/delay.h> > +#include <linux/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> > + > +/* debug level */ > +static int debug; > +module_param(debug, int, 0644); > +MODULE_PARM_DESC(debug, "debug level (0-2)"); > + > +/* Page 0x00 - General Control */ > +#define REG_VERSION 0x0000 > +#define REG_INPUT_SEL 0x0001 > +#define REG_SVC_MODE 0x0002 > +#define REG_HPD_MAN_CTRL 0x0003 > +#define REG_RT_MAN_CTRL 0x0004 > +#define REG_STANDBY_SOFT_RST 0x000A > +#define REG_HDMI_SOFT_RST 0x000B > +#define REG_HDMI_INFO_RST 0x000C > +#define REG_INT_FLG_CLR_TOP 0x000E > +#define REG_INT_FLG_CLR_SUS 0x000F > +#define REG_INT_FLG_CLR_DDC 0x0010 > +#define REG_INT_FLG_CLR_RATE 0x0011 > +#define REG_INT_FLG_CLR_MODE 0x0012 > +#define REG_INT_FLG_CLR_INFO 0x0013 > +#define REG_INT_FLG_CLR_AUDIO 0x0014 > +#define REG_INT_FLG_CLR_HDCP 0x0015 > +#define REG_INT_FLG_CLR_AFE 0x0016 > +#define REG_INT_MASK_TOP 0x0017 > +#define REG_INT_MASK_SUS 0x0018 > +#define REG_INT_MASK_DDC 0x0019 > +#define REG_INT_MASK_RATE 0x001A > +#define REG_INT_MASK_MODE 0x001B > +#define REG_INT_MASK_INFO 0x001C > +#define REG_INT_MASK_AUDIO 0x001D > +#define REG_INT_MASK_HDCP 0x001E > +#define REG_INT_MASK_AFE 0x001F > +#define REG_DETECT_5V 0x0020 > +#define REG_SUS_STATUS 0x0021 > +#define REG_V_PER 0x0022 > +#define REG_H_PER 0x0025 > +#define REG_HS_WIDTH 0x0027 > +#define REG_FMT_H_TOT 0x0029 > +#define REG_FMT_H_ACT 0x002b > +#define REG_FMT_H_FRONT 0x002d > +#define REG_FMT_H_SYNC 0x002f > +#define REG_FMT_H_BACK 0x0031 > +#define REG_FMT_V_TOT 0x0033 > +#define REG_FMT_V_ACT 0x0035 > +#define REG_FMT_V_FRONT_F1 0x0037 > +#define REG_FMT_V_FRONT_F2 0x0038 > +#define REG_FMT_V_SYNC 0x0039 > +#define REG_FMT_V_BACK_F1 0x003a > +#define REG_FMT_V_BACK_F2 0x003b > +#define REG_FMT_DE_ACT 0x003c > +#define REG_RATE_CTRL 0x0040 > +#define REG_CLK_MIN_RATE 0x0043 > +#define REG_CLK_MAX_RATE 0x0046 > +#define REG_CLK_A_STATUS 0x0049 > +#define REG_CLK_A_RATE 0x004A > +#define REG_DRIFT_CLK_A_REG 0x004D > +#define REG_CLK_B_STATUS 0x004E > +#define REG_CLK_B_RATE 0x004F > +#define REG_DRIFT_CLK_B_REG 0x0052 > +#define REG_HDCP_CTRL 0x0060 > +#define REG_HDCP_KDS 0x0061 > +#define REG_HDCP_BCAPS 0x0063 > +#define REG_HDCP_KEY_CTRL 0x0064 > +#define REG_INFO_CTRL 0x0076 > +#define REG_INFO_EXCEED 0x0077 > +#define REG_PIX_REPEAT 0x007B > +#define REG_AUDIO_PATH 0x007C > +#define REG_AUDCFG 0x007D > +#define REG_AUDIO_OUT_ENABLE 0x007E > +#define REG_AUDIO_OUT_HIZ 0x007F > +#define REG_VDP_CTRL 0x0080 > +#define REG_VDP_MATRIX 0x0081 > +#define REG_VHREF_CTRL 0x00A0 > +#define REG_PXCNT_PR 0x00A2 > +#define REG_PXCNT_NPIX 0x00A4 > +#define REG_LCNT_PR 0x00A6 > +#define REG_LCNT_NLIN 0x00A8 > +#define REG_HREF_S 0x00AA > +#define REG_HREF_E 0x00AC > +#define REG_HS_S 0x00AE > +#define REG_HS_E 0x00B0 > +#define REG_VREF_F1_S 0x00B2 > +#define REG_VREF_F1_WIDTH 0x00B4 > +#define REG_VREF_F2_S 0x00B5 > +#define REG_VREF_F2_WIDTH 0x00B7 > +#define REG_VS_F1_LINE_S 0x00B8 > +#define REG_VS_F1_LINE_WIDTH 0x00BA > +#define REG_VS_F2_LINE_S 0x00BB > +#define REG_VS_F2_LINE_WIDTH 0x00BD > +#define REG_VS_F1_PIX_S 0x00BE > +#define REG_VS_F1_PIX_E 0x00C0 > +#define REG_VS_F2_PIX_S 0x00C2 > +#define REG_VS_F2_PIX_E 0x00C4 > +#define REG_FREF_F1_S 0x00C6 > +#define REG_FREF_F2_S 0x00C8 > +#define REG_FDW_S 0x00ca > +#define REG_FDW_E 0x00cc > +#define REG_BLK_GY 0x00da > +#define REG_BLK_BU 0x00dc > +#define REG_BLK_RV 0x00de > +#define REG_FILTERS_CTRL 0x00e0 > +#define REG_DITHERING_CTRL 0x00E9 > +#define REG_OF 0x00EA > +#define REG_PCLK 0x00EB > +#define REG_HS_HREF 0x00EC > +#define REG_VS_VREF 0x00ED > +#define REG_DE_FREF 0x00EE > +#define REG_VP35_32_CTRL 0x00EF > +#define REG_VP31_28_CTRL 0x00F0 > +#define REG_VP27_24_CTRL 0x00F1 > +#define REG_VP23_20_CTRL 0x00F2 > +#define REG_VP19_16_CTRL 0x00F3 > +#define REG_VP15_12_CTRL 0x00F4 > +#define REG_VP11_08_CTRL 0x00F5 > +#define REG_VP07_04_CTRL 0x00F6 > +#define REG_VP03_00_CTRL 0x00F7 > +#define REG_CURPAGE_00H 0xFF > + > +#define MASK_VPER 0x3fffff > +#define MASK_VHREF 0x3fff > +#define MASK_HPER 0x0fff > +#define MASK_HSWIDTH 0x03ff > + > +/* Input Select */ > +#define INPUT_SEL_RST_FMT BIT(7) /* 1=reset format measurement */ > +#define INPUT_SEL_RST_VDP BIT(2) /* 1=reset video data path */ > +#define INPUT_SEL_OUT_MODE BIT(1) /* 0=loop 1=bypass */ > +#define INPUT_SEL_B BIT(0) /* 0=inputA 1=inputB */ > + > +/* Service Mode */ > +#define SVC_MODE_CLK2_MASK 0xc0 > +#define SVC_MODE_CLK2_SHIFT 6 > +#define SVC_MODE_CLK2_XTL 0L > +#define SVC_MODE_CLK2_XTLDIV2 1L > +#define SVC_MODE_CLK2_HDMIX2 3L > +#define SVC_MODE_CLK1_MASK 0x30 > +#define SVC_MODE_CLK1_SHIFT 4 > +#define SVC_MODE_CLK1_XTAL 0L > +#define SVC_MODE_CLK1_XTLDIV2 1L > +#define SVC_MODE_CLK1_HDMI 3L > +#define SVC_MODE_RAMP BIT(3) /* 0=colorbar 1=ramp */ > +#define SVC_MODE_PAL BIT(2) /* 0=NTSC(480i/p) 1=PAL(576i/p) */ > +#define SVC_MODE_INT_PROG BIT(1) /* 0=interlaced 1=progressive */ > +#define SVC_MODE_SM_ON BIT(0) /* Enable color bars and tone gen */ > + > +/* HDP Manual Control */ > +#define HPD_MAN_CTRL_HPD_PULSE BIT(7) /* HPD Pulse low 110ms */ > +#define HPD_MAN_CTRL_5VEN BIT(2) /* Output 5V */ > +#define HPD_MAN_CTRL_HPD_B BIT(1) /* Assert HPD High for Input A */ > +#define HPD_MAN_CTRL_HPD_A BIT(0) /* Assert HPD High for Input A */ > + > +/* RT_MAN_CTRL */ > +#define RT_MAN_CTRL_RT_AUTO BIT(7) > +#define RT_MAN_CTRL_RT BIT(6) > +#define RT_MAN_CTRL_RT_B BIT(1) /* enable TMDS pull-up on Input B */ > +#define RT_MAN_CTRL_RT_A BIT(0) /* enable TMDS pull-up on Input A */ > + > +/* VDP_CTRL */ > +#define VDP_CTRL_COMPDEL_BP BIT(5) /* bypass compdel */ > +#define VDP_CTRL_FORMATTER_BP BIT(4) /* bypass formatter */ > +#define VDP_CTRL_PREFILTER_BP BIT(1) /* bypass prefilter */ > +#define VDP_CTRL_MATRIX_BP BIT(0) /* bypass matrix conversion */ > + > +/* REG_VHREF_CTRL */ > +#define VHREF_INT_DET BIT(7) /* interlace detect: 1=alt 0=frame */ > +#define VHREF_VSYNC_MASK 0x60 > +#define VHREF_VSYNC_SHIFT 6 > +#define VHREF_VSYNC_AUTO 0L > +#define VHREF_VSYNC_FDW 1L > +#define VHREF_VSYNC_EVEN 2L > +#define VHREF_VSYNC_ODD 3L > +#define VHREF_STD_DET_MASK 0x18 > +#define VHREF_STD_DET_SHIFT 3 > +#define VHREF_STD_DET_PAL 0L > +#define VHREF_STD_DET_NTSC 1L > +#define VHREF_STD_DET_AUTO 2L > +#define VHREF_STD_DET_OFF 3L > +#define VHREF_VREF_SRC_STD BIT(2) /* 1=from standard 0=manual */ > +#define VHREF_HREF_SRC_STD BIT(1) /* 1=from standard 0=manual */ > +#define VHREF_HSYNC_SEL_HS BIT(0) /* 1=HS 0=VS */ > + > +/* AUDIO_OUT_ENABLE */ > +#define AUDIO_OUT_ENABLE_ACLK BIT(5) > +#define AUDIO_OUT_ENABLE_WS BIT(4) > +#define AUDIO_OUT_ENABLE_AP3 BIT(3) > +#define AUDIO_OUT_ENABLE_AP2 BIT(2) > +#define AUDIO_OUT_ENABLE_AP1 BIT(1) > +#define AUDIO_OUT_ENABLE_AP0 BIT(0) > + > +/* Prefilter Control */ > +#define FILTERS_CTRL_BU_MASK 0x0c > +#define FILTERS_CTRL_BU_SHIFT 2 > +#define FILTERS_CTRL_RV_MASK 0x03 > +#define FILTERS_CTRL_RV_SHIFT 0 > +#define FILTERS_CTRL_OFF 0L /* off */ > +#define FILTERS_CTRL_2TAP 1L /* 2 Taps */ > +#define FILTERS_CTRL_7TAP 2L /* 7 Taps */ > +#define FILTERS_CTRL_2_7TAP 3L /* 2/7 Taps */ > + > +/* PCLK Configuration */ > +#define PCLK_DELAY_MASK 0x70 > +#define PCLK_DELAY_SHIFT 4 /* Pixel delay (-8..+7) */ > +#define PCLK_INV_SHIFT 2 > +#define PCLK_SEL_MASK 0x03 /* clock scaler */ > +#define PCLK_SEL_SHIFT 0 > +#define PCLK_SEL_X1 0L > +#define PCLK_SEL_X2 1L > +#define PCLK_SEL_DIV2 2L > +#define PCLK_SEL_DIV4 3L > + > +/* Pixel Repeater */ > +#define PIX_REPEAT_MASK_UP_SEL 0x30 > +#define PIX_REPEAT_MASK_REP 0x0f > +#define PIX_REPEAT_SHIFT 4 > +#define PIX_REPEAT_CHROMA 1 > + > +/* Page 0x01 - HDMI info and packets */ > +#define REG_HDMI_FLAGS 0x0100 > +#define REG_DEEP_COLOR_MODE 0x0101 > +#define REG_AUDIO_FLAGS 0x0108 > +#define REG_AUDIO_FREQ 0x0109 > +#define REG_ACP_PACKET_TYPE 0x0141 > +#define REG_ISRC1_PACKET_TYPE 0x0161 > +#define REG_ISRC2_PACKET_TYPE 0x0181 > +#define REG_GBD_PACKET_TYPE 0x01a1 > + > +/* HDMI_FLAGS */ > +#define HDMI_FLAGS_AUDIO BIT(7) /* Audio packet in last videoframe */ > +#define HDMI_FLAGS_HDMI BIT(6) /* HDMI detected */ > +#define HDMI_FLAGS_EESS BIT(5) /* EESS detected */ > +#define HDMI_FLAGS_HDCP BIT(4) /* HDCP detected */ > +#define HDMI_FLAGS_AVMUTE BIT(3) /* AVMUTE */ > +#define HDMI_FLAGS_AUD_LAYOUT BIT(2) /* Layout status Audio sample packet */ > +#define HDMI_FLAGS_AUD_FIFO_OF BIT(1) /* FIFO read/write pointers crossed */ > +#define HDMI_FLAGS_AUD_FIFO_LOW BIT(0) /* FIFO read ptr within 2 of write */ > + > +/* Page 0x12 - HDMI Extra control and debug */ > +#define REG_CLK_CFG 0x1200 > +#define REG_CLK_OUT_CFG 0x1201 > +#define REG_CFG1 0x1202 > +#define REG_CFG2 0x1203 > +#define REG_WDL_CFG 0x1210 > +#define REG_DELOCK_DELAY 0x1212 > +#define REG_PON_OVR_EN 0x12A0 > +#define REG_PON_CBIAS 0x12A1 > +#define REG_PON_RESCAL 0x12A2 > +#define REG_PON_RES 0x12A3 > +#define REG_PON_CLK 0x12A4 > +#define REG_PON_PLL 0x12A5 > +#define REG_PON_EQ 0x12A6 > +#define REG_PON_DES 0x12A7 > +#define REG_PON_OUT 0x12A8 > +#define REG_PON_MUX 0x12A9 > +#define REG_MODE_REC_CFG1 0x12F8 > +#define REG_MODE_REC_CFG2 0x12F9 > +#define REG_MODE_REC_STS 0x12FA > +#define REG_AUDIO_LAYOUT 0x12D0 > + > +#define PON_EN 1 > +#define PON_DIS 0 > + > +/* CLK CFG */ > +#define CLK_CFG_INV_OUT_CLK BIT(7) > +#define CLK_CFG_INV_BUS_CLK BIT(6) > +#define CLK_CFG_SEL_ACLK_EN BIT(1) > +#define CLK_CFG_SEL_ACLK BIT(0) > +#define CLK_CFG_DIS 0 > + > +/* Page 0x13 - HDMI Extra control and debug */ > +#define REG_DEEP_COLOR_CTRL 0x1300 > +#define REG_CGU_DBG_SEL 0x1305 > +#define REG_HDCP_DDC_ADDR 0x1310 > +#define REG_HDCP_KIDX 0x1316 > +#define REG_DEEP_PLL7_BYP 0x1347 > +#define REG_HDCP_DE_CTRL 0x1370 > +#define REG_HDCP_EP_FILT_CTRL 0x1371 > +#define REG_HDMI_CTRL 0x1377 > +#define REG_HMTP_CTRL 0x137a > +#define REG_TIMER_D 0x13CF > +#define REG_SUS_SET_RGB0 0x13E1 > +#define REG_SUS_SET_RGB1 0x13E2 > +#define REG_SUS_SET_RGB2 0x13E3 > +#define REG_SUS_SET_RGB3 0x13E4 > +#define REG_SUS_SET_RGB4 0x13E5 > +#define REG_MAN_SUS_HDMI_SEL 0x13E8 > +#define REG_MAN_HDMI_SET 0x13E9 > +#define REG_SUS_CLOCK_GOOD 0x13EF > + > +/* HDCP DE Control */ > +#define HDCP_DE_MODE_MASK 0xc0 /* DE Measurement mode */ > +#define HDCP_DE_MODE_SHIFT 6 > +#define HDCP_DE_REGEN_EN BIT(5) /* enable regen mode */ > +#define HDCP_DE_FILTER_MASK 0x18 /* DE filter sensitivity */ > +#define HDCP_DE_FILTER_SHIFT 3 > +#define HDCP_DE_COMP_MASK 0x07 /* DE Composition mode */ > +#define HDCP_DE_COMP_MIXED 6L > +#define HDCP_DE_COMP_OR 5L > +#define HDCP_DE_COMP_AND 4L > +#define HDCP_DE_COMP_CH3 3L > +#define HDCP_DE_COMP_CH2 2L > +#define HDCP_DE_COMP_CH1 1L > +#define HDCP_DE_COMP_CH0 0L > + > +/* HDCP EP Filter Control */ > +#define HDCP_EP_FIL_CTL_MASK 0x30 > +#define HDCP_EP_FIL_CTL_SHIFT 4 > +#define HDCP_EP_FIL_VS_MASK 0x0c > +#define HDCP_EP_FIL_VS_SHIFT 2 > +#define HDCP_EP_FIL_HS_MASK 0x03 > +#define HDCP_EP_FIL_HS_SHIFT 0 > + > +/* HDMI_CTRL */ > +#define HDMI_CTRL_MUTE_MASK 0x0c > +#define HDMI_CTRL_MUTE_SHIFT 2 > +#define HDMI_CTRL_MUTE_AUTO 0L > +#define HDMI_CTRL_MUTE_OFF 1L > +#define HDMI_CTRL_MUTE_ON 2L > +#define HDMI_CTRL_HDCP_MASK 0x03 > +#define HDMI_CTRL_HDCP_SHIFT 0 > +#define HDMI_CTRL_HDCP_EESS 2L > +#define HDMI_CTRL_HDCP_OESS 1L > +#define HDMI_CTRL_HDCP_AUTO 0L > + > +/* CGU_DBG_SEL bits */ > +#define CGU_DBG_CLK_SEL_MASK 0x18 > +#define CGU_DBG_CLK_SEL_SHIFT 3 > +#define CGU_DBG_XO_FRO_SEL BIT(2) > +#define CGU_DBG_VDP_CLK_SEL BIT(1) > +#define CGU_DBG_PIX_CLK_SEL BIT(0) > + > +/* REG_MAN_SUS_HDMI_SEL / REG_MAN_HDMI_SET bits */ > +#define MAN_DIS_OUT_BUF BIT(7) > +#define MAN_DIS_ANA_PATH BIT(6) > +#define MAN_DIS_HDCP BIT(5) > +#define MAN_DIS_TMDS_ENC BIT(4) > +#define MAN_DIS_TMDS_FLOW BIT(3) > +#define MAN_RST_HDCP BIT(2) > +#define MAN_RST_TMDS_ENC BIT(1) > +#define MAN_RST_TMDS_FLOW BIT(0) > + > +/* Page 0x14 - Audio Extra control and debug */ > +#define REG_FIFO_LATENCY_VAL 0x1403 > +#define REG_AUDIO_CLOCK 0x1411 > +#define REG_TEST_NCTS_CTRL 0x1415 > +#define REG_TEST_AUDIO_FREQ 0x1426 > +#define REG_TEST_MODE 0x1437 > + > +/* Audio Clock Configuration */ > +#define AUDIO_CLOCK_PLL_PD BIT(7) /* powerdown PLL */ > +#define AUDIO_CLOCK_SEL_MASK 0x7f > +#define AUDIO_CLOCK_SEL_16FS 0L /* 16*fs */ > +#define AUDIO_CLOCK_SEL_32FS 1L /* 32*fs */ > +#define AUDIO_CLOCK_SEL_64FS 2L /* 64*fs */ > +#define AUDIO_CLOCK_SEL_128FS 3L /* 128*fs */ > +#define AUDIO_CLOCK_SEL_256FS 4L /* 256*fs */ > +#define AUDIO_CLOCK_SEL_512FS 5L /* 512*fs */ > + > +/* Page 0x20: EDID and Hotplug Detect */ > +#define REG_EDID_IN_BYTE0 0x2000 /* EDID base */ > +#define REG_EDID_IN_VERSION 0x2080 > +#define REG_EDID_ENABLE 0x2081 > +#define REG_HPD_POWER 0x2084 > +#define REG_HPD_AUTO_CTRL 0x2085 > +#define REG_HPD_DURATION 0x2086 > +#define REG_RX_HPD_HEAC 0x2087 > + > +/* EDID_ENABLE */ > +#define EDID_ENABLE_NACK_OFF BIT(7) > +#define EDID_ENABLE_EDID_ONLY BIT(6) > +#define EDID_ENABLE_B_EN BIT(1) > +#define EDID_ENABLE_A_EN BIT(0) > + > +/* HPD Power */ > +#define HPD_POWER_BP_MASK 0x0c > +#define HPD_POWER_BP_SHIFT 2 > +#define HPD_POWER_BP_LOW 0L > +#define HPD_POWER_BP_HIGH 1L > +#define HPD_POWER_EDID_ONLY BIT(1) > + > +/* HPD Auto control */ > +#define HPD_AUTO_READ_EDID BIT(7) > +#define HPD_AUTO_HPD_F3TECH BIT(5) > +#define HPD_AUTO_HP_OTHER BIT(4) > +#define HPD_AUTO_HPD_UNSEL BIT(3) > +#define HPD_AUTO_HPD_ALL_CH BIT(2) > +#define HPD_AUTO_HPD_PRV_CH BIT(1) > +#define HPD_AUTO_HPD_NEW_CH BIT(0) > + > +/* Page 0x21 - EDID content */ > +#define REG_EDID_IN_BYTE128 0x2100 /* CEA Extension block */ > +#define REG_EDID_IN_SPA_SUB 0x2180 > +#define REG_EDID_IN_SPA_AB_A 0x2181 > +#define REG_EDID_IN_SPA_CD_A 0x2182 > +#define REG_EDID_IN_CKSUM_A 0x2183 > +#define REG_EDID_IN_SPA_AB_B 0x2184 > +#define REG_EDID_IN_SPA_CD_B 0x2185 > +#define REG_EDID_IN_CKSUM_B 0x2186 > + > +/* Page 0x30 - NV Configuration */ > +#define REG_RT_AUTO_CTRL 0x3000 > +#define REG_EQ_MAN_CTRL0 0x3001 > +#define REG_EQ_MAN_CTRL1 0x3002 > +#define REG_OUTPUT_CFG 0x3003 > +#define REG_MUTE_CTRL 0x3004 > +#define REG_SLAVE_ADDR 0x3005 > +#define REG_CMTP_REG6 0x3006 > +#define REG_CMTP_REG7 0x3007 > +#define REG_CMTP_REG8 0x3008 > +#define REG_CMTP_REG9 0x3009 > +#define REG_CMTP_REGA 0x300A > +#define REG_CMTP_REGB 0x300B > +#define REG_CMTP_REGC 0x300C > +#define REG_CMTP_REGD 0x300D > +#define REG_CMTP_REGE 0x300E > +#define REG_CMTP_REGF 0x300F > +#define REG_CMTP_REG10 0x3010 > +#define REG_CMTP_REG11 0x3011 > + > +/* Page 0x80 - CEC */ > +#define REG_PWR_CONTROL 0x80F4 > +#define REG_OSC_DIVIDER 0x80F5 > +#define REG_EN_OSC_PERIOD_LSB 0x80F8 > +#define REG_CONTROL 0x80FF > + > +/* global interrupt flags (INT_FLG_CRL_TOP) */ > +#define INTERRUPT_AFE BIT(7) /* AFE module */ > +#define INTERRUPT_HDCP BIT(6) /* HDCP module */ > +#define INTERRUPT_AUDIO BIT(5) /* Audio module */ > +#define INTERRUPT_INFO BIT(4) /* Infoframe module */ > +#define INTERRUPT_MODE BIT(3) /* HDMI mode module */ > +#define INTERRUPT_RATE BIT(2) /* rate module */ > +#define INTERRUPT_DDC BIT(1) /* DDC module */ > +#define INTERRUPT_SUS BIT(0) /* SUS module */ > + > +/* INT_FLG_CLR_HDCP bits */ > +#define MASK_HDCP_MTP BIT(7) /* HDCP MTP busy */ > +#define MASK_HDCP_DLMTP BIT(4) /* HDCP end download MTP to SRAM */ > +#define MASK_HDCP_DLRAM BIT(3) /* HDCP end download keys from SRAM */ > +#define MASK_HDCP_ENC BIT(2) /* HDCP ENC */ > +#define MASK_STATE_C5 BIT(1) /* HDCP State C5 reached */ > +#define MASK_AKSV BIT(0) /* AKSV received (start of auth) */ > + > +/* INT_FLG_CLR_RATE bits */ > +#define MASK_RATE_B_DRIFT BIT(7) /* Rate measurement drifted */ > +#define MASK_RATE_B_ST BIT(6) /* Rate measurement stability change */ > +#define MASK_RATE_B_ACT BIT(5) /* Rate measurement activity change */ > +#define MASK_RATE_B_PST BIT(4) /* Rate measreument presence change */ > +#define MASK_RATE_A_DRIFT BIT(3) /* Rate measurement drifted */ > +#define MASK_RATE_A_ST BIT(2) /* Rate measurement stability change */ > +#define MASK_RATE_A_ACT BIT(1) /* Rate measurement presence change */ > +#define MASK_RATE_A_PST BIT(0) /* Rate measreument presence change */ > + > +/* INT_FLG_CLR_SUS (Start Up Sequencer) bits */ > +#define MASK_MPT BIT(7) /* Config MTP end of process */ > +#define MASK_FMT BIT(5) /* Video format changed */ > +#define MASK_RT_PULSE BIT(4) /* End of termination resistance pulse */ > +#define MASK_SUS_END BIT(3) /* SUS last state reached */ > +#define MASK_SUS_ACT BIT(2) /* Activity of selected input changed */ > +#define MASK_SUS_CH BIT(1) /* Selected input changed */ > +#define MASK_SUS_ST BIT(0) /* SUS state changed */ > + > +/* INT_FLG_CLR_DDC bits */ > +#define MASK_EDID_MTP BIT(7) /* EDID MTP end of process */ > +#define MASK_DDC_ERR BIT(6) /* master DDC error */ > +#define MASK_DDC_CMD_DONE BIT(5) /* master DDC cmd send correct */ > +#define MASK_READ_DONE BIT(4) /* End of down EDID read */ > +#define MASK_RX_DDC_SW BIT(3) /* Output DDC switching finished */ > +#define MASK_HDCP_DDC_SW BIT(2) /* HDCP DDC switching finished */ > +#define MASK_HDP_PULSE_END BIT(1) /* End of Hot Plug Detect pulse */ > +#define MASK_DET_5V BIT(0) /* Detection of +5V */ > + > +/* INT_FLG_CLR_MODE bits */ > +#define MASK_HDMI_FLG BIT(7) /* HDMI mode/avmute/encrypt/FIFO fail */ > +#define MASK_GAMUT BIT(6) /* Gamut packet */ > +#define MASK_ISRC2 BIT(5) /* ISRC2 packet */ > +#define MASK_ISRC1 BIT(4) /* ISRC1 packet */ > +#define MASK_ACP BIT(3) /* Audio Content Protection packet */ > +#define MASK_DC_NO_GCP BIT(2) /* GCP not received in 5 frames */ > +#define MASK_DC_PHASE BIT(1) /* deepcolor pixel phase needs update */ > +#define MASK_DC_MODE BIT(0) /* deepcolor color depth changed */ > + > +/* INT_FLG_CLR_INFO bits (Infoframe Change Status) */ > +#define MASK_MPS_IF BIT(6) /* MPEG Source Product */ > +#define MASK_AUD_IF BIT(5) /* Audio */ > +#define MASK_SPD_IF BIT(4) /* Source Product Descriptor */ > +#define MASK_AVI_IF BIT(3) /* Auxiliary Video IF */ > +#define MASK_VS_IF_OTHER_BK2 BIT(2) /* Vendor Specific (bank2) */ > +#define MASK_VS_IF_OTHER_BK1 BIT(1) /* Vendor Specific (bank1) */ > +#define MASK_VS_IF_HDMI BIT(0) /* Vendor Specific (w/ HDMI LLC code) */ > + > +/* INT_FLG_CLR_AUDIO bits */ > +#define MASK_AUDIO_FREQ_FLG BIT(5) /* Audio freq change */ > +#define MASK_AUDIO_FLG BIT(4) /* DST, OBA, HBR, ASP change */ > +#define MASK_MUTE_FLG BIT(3) /* Audio Mute */ > +#define MASK_CH_STATE BIT(2) /* Channel status */ > +#define MASK_UNMUTE_FIFO BIT(1) /* Audio Unmute */ > +#define MASK_ERROR_FIFO_PT BIT(0) /* Audio FIFO pointer error */ > + > +/* INT_FLG_CLR_AFE bits */ > +#define MASK_AFE_WDL_UNLOCKED BIT(7) /* Wordlocker was unlocked */ > +#define MASK_AFE_GAIN_DONE BIT(6) /* Gain calibration done */ > +#define MASK_AFE_OFFSET_DONE BIT(5) /* Offset calibration done */ > +#define MASK_AFE_ACTIVITY_DET BIT(4) /* Activity detected on data */ > +#define MASK_AFE_PLL_LOCK BIT(3) /* TMDS PLL is locked */ > +#define MASK_AFE_TRMCAL_DONE BIT(2) /* Termination calibration done */ > +#define MASK_AFE_ASU_STATE BIT(1) /* ASU state is reached */ > +#define MASK_AFE_ASU_READY BIT(0) /* AFE calibration done: TMDS ready */ > + > +/* Audio Output */ > +#define AUDCFG_CLK_INVERT BIT(7) /* invert A_CLK polarity */ > +#define AUDCFG_TEST_TONE BIT(6) /* enable test tone generator */ > +#define AUDCFG_BUS_SHIFT 5 > +#define AUDCFG_BUS_I2S 0L > +#define AUDCFG_BUS_SPDIF 1L > +#define AUDCFG_I2SW_SHIFT 4 > +#define AUDCFG_I2SW_16 0L > +#define AUDCFG_I2SW_32 1L > +#define AUDCFG_AUTO_MUTE_EN BIT(3) /* Enable Automatic audio mute */ > +#define AUDCFG_HBR_SHIFT 2 > +#define AUDCFG_HBR_STRAIGHT 0L /* straight via AP0 */ > +#define AUDCFG_HBR_DEMUX 1L /* demuxed via AP0:AP3 */ > +#define AUDCFG_TYPE_MASK 0x03 > +#define AUDCFG_TYPE_SHIFT 0 > +#define AUDCFG_TYPE_DST 3L /* Direct Stream Transfer (DST) */ > +#define AUDCFG_TYPE_OBA 2L /* One Bit Audio (OBA) */ > +#define AUDCFG_TYPE_HBR 1L /* High Bit Rate (HBR) */ > +#define AUDCFG_TYPE_PCM 0L /* Audio samples */ > + > +/* Video Formatter */ > +#define OF_VP_ENABLE BIT(7) /* VP[35:0]/HS/VS/DE/CLK */ > +#define OF_BLK BIT(4) /* blanking codes */ > +#define OF_TRC BIT(3) /* timing codes (SAV/EAV) */ > +#define OF_FMT_MASK 0x3 > +#define OF_FMT_444 0L /* RGB444/YUV444 */ > +#define OF_FMT_422_SMPT 1L /* YUV422 semi-planar */ > +#define OF_FMT_422_CCIR 2L /* YUV422 CCIR656 */ > + > +/* HS/HREF output control */ > +#define HS_HREF_DELAY_MASK 0xf0 > +#define HS_HREF_DELAY_SHIFT 4 /* Pixel delay (-8..+7) */ > +#define HS_HREF_PXQ_SHIFT 3 /* Timing codes from HREF */ > +#define HS_HREF_INV_SHIFT 2 /* polarity (1=invert) */ > +#define HS_HREF_SEL_MASK 0x03 > +#define HS_HREF_SEL_SHIFT 0 > +#define HS_HREF_SEL_HS_VHREF 0L /* HS from VHREF */ > +#define HS_HREF_SEL_HREF_VHREF 1L /* HREF from VHREF */ > +#define HS_HREF_SEL_HREF_HDMI 2L /* HREF from HDMI */ > +#define HS_HREF_SEL_NONE 3L /* not generated */ > + > +/* VS output control */ > +#define VS_VREF_DELAY_MASK 0xf0 > +#define VS_VREF_DELAY_SHIFT 4 /* Pixel delay (-8..+7) */ > +#define VS_VREF_INV_SHIFT 2 /* polarity (1=invert) */ > +#define VS_VREF_SEL_MASK 0x03 > +#define VS_VREF_SEL_SHIFT 0 > +#define VS_VREF_SEL_VS_VHREF 0L /* VS from VHREF */ > +#define VS_VREF_SEL_VREF_VHREF 1L /* VREF from VHREF */ > +#define VS_VREF_SEL_VREF_HDMI 2L /* VREF from HDMI */ > +#define VS_VREF_SEL_NONE 3L /* not generated */ > + > +/* DE/FREF output control */ > +#define DE_FREF_DELAY_MASK 0xf0 > +#define DE_FREF_DELAY_SHIFT 4 /* Pixel delay (-8..+7) */ > +#define DE_FREF_DE_PXQ_SHIFT 3 /* Timing codes from DE */ > +#define DE_FREF_INV_SHIFT 2 /* polarity (1=invert) */ > +#define DE_FREF_SEL_MASK 0x03 > +#define DE_FREF_SEL_SHIFT 0 > +#define DE_FREF_SEL_DE_VHREF 0L /* DE from VHREF (HREF and not(VREF) */ > +#define DE_FREF_SEL_FREF_VHREF 1L /* FREF from VHREF */ > +#define DE_FREF_SEL_FREF_HDMI 2L /* FREF from HDMI */ > +#define DE_FREF_SEL_NONE 3L /* not generated */ > + > +/* HDMI_SOFT_RST bits */ > +#define RESET_DC BIT(7) /* Reset deep color module */ > +#define RESET_HDCP BIT(6) /* Reset HDCP module */ > +#define RESET_KSV BIT(5) /* Reset KSV-FIFO */ > +#define RESET_SCFG BIT(4) /* Reset HDCP and repeater function */ > +#define RESET_HCFG BIT(3) /* Reset HDCP DDC part */ > +#define RESET_PA BIT(2) /* Reset polarity adjust */ > +#define RESET_EP BIT(1) /* Reset Error protection */ > +#define RESET_TMDS BIT(0) /* Reset TMDS (calib, encoding, flow) */ > + > +/* HDMI_INFO_RST bits */ > +#define NACK_HDCP BIT(7) /* No ACK on HDCP request */ > +#define RESET_FIFO BIT(4) /* Reset Audio FIFO control */ > +#define RESET_GAMUT BIT(3) /* Clear Gamut packet */ > +#define RESET_AI BIT(2) /* Clear ACP and ISRC packets */ > +#define RESET_IF BIT(1) /* Clear all Audio infoframe packets */ > +#define RESET_AUDIO BIT(0) /* Reset Audio FIFO control */ > + > +/* HDCP_BCAPS bits */ > +#define HDCP_HDMI BIT(7) /* HDCP suports HDMI (vs DVI only) */ > +#define HDCP_REPEATER BIT(6) /* HDCP supports repeater function */ > +#define HDCP_READY BIT(5) /* set by repeater function */ > +#define HDCP_FAST BIT(4) /* Up to 400kHz */ > +#define HDCP_11 BIT(1) /* HDCP 1.1 supported */ > +#define HDCP_FAST_REAUTH BIT(0) /* fast reauthentication supported */ > + > +/* Audio output formatter */ > +#define AUDIO_LAYOUT_SP_FLAG BIT(2) /* sp flag used by FIFO */ > +#define AUDIO_LAYOUT_MANUAL BIT(1) /* manual layout (vs per pkt) */ > +#define AUDIO_LAYOUT_LAYOUT1 BIT(0) /* Layout1: AP0-3 vs Layout0:AP0 */ > + > +/* masks for interrupt status registers */ > +#define MASK_SUS_STATUS 0x1F > +#define LAST_STATE_REACHED 0x1B > +#define MASK_CLK_STABLE 0x04 > +#define MASK_CLK_ACTIVE 0x02 > +#define MASK_SUS_STATE 0x10 > +#define MASK_SR_FIFO_FIFO_CTRL 0x30 > +#define MASK_AUDIO_FLAG 0x10 > + > +/* Rate measurement */ > +#define RATE_REFTIM_ENABLE 0x01 > +#define CLK_MIN_RATE 0x0057e4 > +#define CLK_MAX_RATE 0x0395f8 > +#define WDL_CFG_VAL 0x82 > +#define DC_FILTER_VAL 0x31 > + > +/* Infoframe */ > +#define VS_HDMI_IF_UPDATE 0x0200 > +#define VS_HDMI_IF 0x0201 > +#define VS_BK1_IF_UPDATE 0x0220 > +#define VS_BK1_IF 0x0221 > +#define VS_BK2_IF_UPDATE 0x0240 > +#define VS_BK2_IF 0x0241 > +#define AVI_IF_UPDATE 0x0260 > +#define AVI_IF 0x0261 > +#define SPD_IF_UPDATE 0x0280 > +#define SPD_IF 0x0281 > +#define AUD_IF_UPDATE 0x02a0 > +#define AUD_IF 0x02a1 > +#define MPS_IF_UPDATE 0x02c0 > +#define MPS_IF 0x02c1 > + > +/* 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 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", > +}; > + > +/* > + * Video Output formats > + * There are 24 video output pins on TDA19971 and 36 on TDA19973 supporting > + * the following output formats: > + * - RGB444 > + * - YUV444 > + * - YUV422 semi-planar based on ITU-R BT.601 > + * - YUV422 ITU-R BT.656 > + * > + * TDA19971 can output 3x8bits per pixel > + * TDA19973 can output 3x8, 3x10, or 3x12bit per pixel > + * > + * Deep color modes (3x10 or 3x12 bits) are possible in any case. > + * > + * Reference: NXP AN1206 - TDA19971_TDA19973 receiver HW recommendation: 3.3.4 > + */ > +static u32 tda19971_video_formats[] = { > + /* 24bit RGB444: 1 pixel in 1x24bit sample: VP[23:0] */ > + MEDIA_BUS_FMT_RGB888_1X24, > + /* 24bit YUV444: 1 pixel in 1x24bit sample: VP[23:0] */ > + MEDIA_BUS_FMT_YUV8_1X24, > + /* 24bit YUV422: 1 pixel in 1x24bit sample: VP[23:12]/VP[11:0] */ > + MEDIA_BUS_FMT_UYVY12_1X24, > + /* 20bit YUV422: 1 pixel in 1x20bit sample: VP[23:14]/VP[11:2] */ > + MEDIA_BUS_FMT_UYVY10_1X20, > + /* 16bit YUV422: 1 pixel in 1x16bit sample: VP[23:16]/VP[15:8] */ > + MEDIA_BUS_FMT_UYVY8_1X16, > + /* 12bit CCIR656: 1 pixel in 2x12bit samples: VP[23:12] */ > + MEDIA_BUS_FMT_UYVY12_2X12, > + /* 10bit CCIR656: 1 pixel in 2x10bit samples: VP[23:14] */ > + MEDIA_BUS_FMT_UYVY10_2X10, > + /* 8bit CCIR656: 1 pixel in 2x8bit samples: VP[23:16] */ > + MEDIA_BUS_FMT_UYVY8_2X8, > +}; > +static u32 tda19973_video_formats[] = { > + /* 36bit RGB444: 1 pixel in 1x36bit sample on VP[35:0] */ > + MEDIA_BUS_FMT_RGB121212_1X36, > + /* 36bit YUV444 1 pixel in 1x36bit sample on VP[35:0] */ > + MEDIA_BUS_FMT_YUV12_1X36, > + /* 24bit YUV422: 1 pixel in 1x24bit sample on VP[35:24]/VP[11:0] */ > + MEDIA_BUS_FMT_UYVY12_1X24, > + /* 12bit CCIR656: 1 pixel in 2x12bit samples on VP[11:0] */ > + MEDIA_BUS_FMT_UYVY12_2X12, > +}; > + > +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 */ > + 480, 1080, /* min/max height */ > + 13000000, 165000000, /* min/max pixelclock */ > + V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT, /* standards */ > + V4L2_DV_BT_CAP_PROGRESSIVE /* capabilities */ > + ) > +}; > + > +/* > + * Video Input formats > + */ > +struct vhref_values { > + u16 href_start; > + u16 href_end; > + u16 vref_f1_start; > + u8 vref_f1_width; > + u16 vref_f2_start; > + u8 vref_f2_width; > + u16 fieldref_f1_start; > + u8 fieldPolarity; > + u16 fieldref_f2_start; Since we don't support interlaced (yet) I'd just drop the 'f2' fields. Ditto for fieldPolarity. Can't these href/vref values be calculated from the timings? > +}; > + > +struct tda1997x_video_std { > + const struct v4l2_dv_timings timings; > + const struct vhref_values vhref_values; > +}; > + > +static const struct tda1997x_video_std tda1997x_hdmi_modes[] = { > + /* Low TV */ > + { V4L2_DV_BT_CEA_1280X720P24, > + {261, 1541, 745, 30, 0, 0, 1, 0, 0} > + }, > + { V4L2_DV_BT_CEA_1280X720P25, > + {261, 1541, 745, 30, 0, 0, 1, 0, 0} > + }, > + { V4L2_DV_BT_CEA_1280X720P30, > + {261, 1541, 745, 30, 0, 0, 1, 0, 0} > + }, > + { V4L2_DV_BT_CEA_1920X1080P24, > + {193, 2113, 1121, 45, 0, 0, 1, 0, 0} > + }, > + { V4L2_DV_BT_CEA_1920X1080P25, > + {193, 2113, 1121, 45, 0, 0, 1, 0, 0} > + }, > + { V4L2_DV_BT_CEA_1920X1080P30, > + {193, 2113, 1121, 45, 0, 0, 1, 0, 0} > + }, > + > + /* 60 Hz TV */ > + { V4L2_DV_BT_CEA_720X480P59_94, > + {123, 843, 516, 45, 0, 0, 1, 0, 0} > + }, > + { V4L2_DV_BT_CEA_1280X720P60, > + {261, 1541, 745, 30, 0, 0, 1, 0, 0} > + }, > + { V4L2_DV_BT_CEA_1920X1080I60, > + {193, 2113, 1123, 22, 560, 23, 1, 0, 563} > + }, > + { V4L2_DV_BT_CEA_720X480I59_94, > + {120, 840, 521, 22, 258, 23, 1, 0, 263} > + }, > + { V4L2_DV_BT_CEA_1920X1080P60, > + {193, 2113, 1121, 45, 0, 0, 1, 0, 0} > + }, > + > + /* 50 Hz TV */ > + { V4L2_DV_BT_CEA_720X576P50, > + {133, 853, 620, 49, 0, 0, 1, 0, 0} > + }, > + { V4L2_DV_BT_CEA_1280X720P50, > + {261, 1541, 745, 30, 0, 0, 1, 0, 0} > + }, > + { V4L2_DV_BT_CEA_1920X1080I50, > + {193, 2113, 1123, 22, 560, 23, 1, 0, 563} > + }, > + { V4L2_DV_BT_CEA_720X576I50, > + {133, 853, 623, 24, 310, 25, 1, 0, 313 } > + }, > + { V4L2_DV_BT_CEA_1920X1080P50, > + {193, 2113, 1121, 45, 0, 0, 1, 0, 0} > + }, > + > + /* 60 Hz PC */ > + { V4L2_DV_BT_DMT_640X480P60, > + {145, 785, 515, 45, 0, 0, 1, 0, 0} > + }, > + { V4L2_DV_BT_DMT_800X600P60, > + {217, 1017, 627, 28, 0, 0, 0, 0, 0} > + }, > + { V4L2_DV_BT_DMT_1024X768P60, > + {297, 1321, 803, 38, 0, 0, 0, 0, 0} > + }, > + { V4L2_DV_BT_DMT_1280X768P60, > + {321, 1601, 795, 30, 0, 0, 0, 0, 0} > + }, > + { V4L2_DV_BT_DMT_1280X960P60, > + {425, 1705, 999, 40, 0, 0, 0, 0, 0} > + }, > + { V4L2_DV_BT_DMT_1280X1024P60, > + {361, 1641, 1065, 42, 0, 0, 0, 0, 0} > + }, > + { V4L2_DV_BT_DMT_1440X900P60, > + {385, 1825, 931, 34, 0, 0, 0, 0, 0} > + }, > + { V4L2_DV_BT_DMT_1600X1200P60, > + {497, 2097, 1249, 50, 0, 0, 0, 0, 0} > + }, > + { V4L2_DV_BT_DMT_1680X1050P60_RB, > + {113, 1793, 1077, 30, 0, 0, 0, 0, 0} > + }, > + { V4L2_DV_BT_DMT_1920X1200P60_RB, > + {113, 2033, 1232, 35, 0, 0, 0, 0, 0} > + }, > + > + /* 75 HZ PC */ > + { V4L2_DV_BT_DMT_640X480P75, > + {185, 825, 499, 20, 0, 0, 1, 0, 0} > + }, > + { V4L2_DV_BT_DMT_800X600P75, > + {241, 1041, 624, 25, 0, 0, 0, 0, 0} > + }, > + { V4L2_DV_BT_DMT_1024X768P75, > + {273, 1297, 799, 32, 0, 0, 0, 0, 0} > + }, > + { V4L2_DV_BT_DMT_1280X768P75, > + {337, 1617, 802, 37, 0, 0, 0, 0, 0} > + }, > + { V4L2_DV_BT_DMT_1280X1024P75, > + {393, 1673, 1065, 42, 0, 0, 0, 0, 0} > + }, > + > + /* 85 HZ PC */ > + { V4L2_DV_BT_DMT_640X480P85, > + {137, 777, 508, 29, 0, 0, 1, 0, 0} > + }, > + { V4L2_DV_BT_DMT_800X600P85, > + {217, 1017, 630, 31, 0, 0, 0, 0, 0} > + }, > + { V4L2_DV_BT_DMT_1024X768P85, > + {305, 1329, 807, 40, 0, 0, 0, 0, 0} > + }, > + { V4L2_DV_BT_DMT_1280X768P85, > + {353, 1633, 905, 140, 0, 0, 0, 0, 0} > + }, > + { V4L2_DV_BT_DMT_1280X1024P85, > + {385, 1665, 1071, 48, 0, 0, 0, 0, 0} > + }, > + > + { V4L2_DV_BT_DMT_1360X768P60, > + {369, 1729, 792, 27, 0, 0, 0, 0, 0} > + }, > + { V4L2_DV_BT_DMT_1400X1050P60, > + {377, 1777, 1086, 39, 0, 0, 0, 0, 0} > + }, > + { V4L2_DV_BT_DMT_1400X1050P60_RB, > + {113, 1513, 1077, 30, 0, 0, 0, 0, 0} > + }, > + { V4L2_DV_BT_DMT_1024X768P70, > + {281, 1305, 803, 38, 0, 0, 0, 0, 0} > + }, > + { V4L2_DV_BT_DMT_640X480P72, > + {169, 809, 511, 40, 0, 0, 0, 0, 0} > + }, > + { V4L2_DV_BT_DMT_800X600P72, > + {185, 985, 629, 66, 0, 0, 0, 0, 0} > + }, > + { V4L2_DV_BT_DMT_640X350P85, > + {161, 801, 413, 95, 0, 0, 0, 0, 0} > + }, > + { V4L2_DV_BT_DMT_640X400P85, > + {161, 801, 444, 45, 0, 0, 0, 0, 0} > + }, > + { V4L2_DV_BT_DMT_720X400P85, > + {181, 901, 445, 46, 0, 0, 0, 0, 0} > + }, > +}; > + > +/* 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; > + const u32 *formats; > + unsigned int nformats; > +}; > + > +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 state_c5_reached; > + char activity_status; > + char input_detect[2]; > + char vendor[12]; > + char product[18]; > + > + /* video */ > + enum hdmi_colorspace colorspace; > + enum hdmi_colorimetry colorimetry; > + const struct tda1997x_video_std *std; > + struct v4l2_dv_timings timings; > + int fps; > + u32 code; > + enum v4l2_mbus_type bus_type; > + char vid_fmt; > + > + /* 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; > +}; > + > +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", > + .formats = tda19971_video_formats, > + .nformats = ARRAY_SIZE(tda19971_video_formats), > + }, > + [TDA19973] = { > + .type = TDA19973, > + .name = "tda19973", > + .formats = tda19973_video_formats, > + .nformats = ARRAY_SIZE(tda19973_video_formats), > + }, > +}; > + > +static inline struct tda1997x_state *to_state(struct v4l2_subdev *sd) > +{ > + return container_of(sd, struct tda1997x_state, 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) > +{ > + u8 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) > +{ > + u8 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; > + u8 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; > +} > + > +/* ----------------------------------------------------------------------------- > + * Signal Control > + */ > + > +/* > + * The color conversion matrix will convert between the colorimetry of the > + * HDMI input to the desired output format RGB|YUV > + */ > +static int > +tda1997x_configure_conv(struct v4l2_subdev *sd, > + enum hdmi_colorspace colorspace, > + enum hdmi_colorimetry colorimetry) > +{ > + struct tda1997x_state *state = to_state(sd); > + /* Colorspace conversion matrix coefficients and offsets */ > + struct color_matrix_coefs { > + /* 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; > + }; > + /* Conversion matrixes */ > + enum { > + ITU709_RGBLIMITED, > + RGBLIMITED_ITU601, > + ITU601_RGBLIMITED, > + }; > + static const struct color_matrix_coefs conv_matrix[] = { > + /* ITU709 -> RGBLimited */ > + { > + -256, -2048, -2048, > + 4096, -1875, -750, > + 4096, 6307, 0, > + 4096, 0, 7431, > + 256, 256, 256, > + }, > + /* RGBLimited -> ITU601 */ > + { > + -256, -256, -256, > + 2404, 1225, 467, > + -1754, 2095, -341, > + -1388, -707, 2095, > + 256, 2048, 2048, > + }, > + /* YUV601 -> RGBLimited */ > + { > + -256, -2048, -2048, > + 4096, -2860, -1378, > + 4096, 5615, 0, > + 4096, 0, 7097, > + 256, 256, 256, > + }, > + }; > + /* 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 color_matrix_coefs *coefficients = NULL; > + const struct blanking_codes *blanking_codes = NULL; > + u8 reg; > + > + v4l_dbg(1, debug, state->client, "%s\n", __func__); > + switch (state->vid_fmt) { > + /* RGB444 */ > + case OF_FMT_444: > + blanking_codes = &rgb_blanking; > + if (colorspace != HDMI_COLORSPACE_RGB) { > + if (colorimetry == HDMI_COLORIMETRY_ITU_709) > + coefficients = &conv_matrix[ITU709_RGBLIMITED]; > + else > + coefficients = &conv_matrix[ITU601_RGBLIMITED]; > + } > + break; > + > + /* YUV422 */ > + case OF_FMT_422_SMPT: /* semi-planar */ > + case OF_FMT_422_CCIR: /* CCIR656 */ > + blanking_codes = &yuv_blanking; > + if (colorspace == HDMI_COLORSPACE_RGB) > + coefficients = &conv_matrix[RGBLIMITED_ITU601]; > + break; > + } > + > + if (coefficients) { > + /* 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, coefficients->offint1); > + io_write16(sd, REG_VDP_MATRIX + 2, coefficients->offint2); > + io_write16(sd, REG_VDP_MATRIX + 4, coefficients->offint3); > + /* coefficients */ > + io_write16(sd, REG_VDP_MATRIX + 6, coefficients->p11coef); > + io_write16(sd, REG_VDP_MATRIX + 8, coefficients->p12coef); > + io_write16(sd, REG_VDP_MATRIX + 10, coefficients->p13coef); > + io_write16(sd, REG_VDP_MATRIX + 12, coefficients->p21coef); > + io_write16(sd, REG_VDP_MATRIX + 14, coefficients->p22coef); > + io_write16(sd, REG_VDP_MATRIX + 16, coefficients->p23coef); > + io_write16(sd, REG_VDP_MATRIX + 18, coefficients->p31coef); > + io_write16(sd, REG_VDP_MATRIX + 20, coefficients->p32coef); > + io_write16(sd, REG_VDP_MATRIX + 22, coefficients->p33coef); > + /* offset outputs */ > + io_write16(sd, REG_VDP_MATRIX + 24, coefficients->offout1); > + io_write16(sd, REG_VDP_MATRIX + 26, coefficients->offout2); > + io_write16(sd, REG_VDP_MATRIX + 28, coefficients->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); > + } > + > + return 0; > +} > + > +/* Configure frame detection window and VHREF timing generator */ > +static int > +tda1997x_configure_input_resolution(struct v4l2_subdev *sd, > + const struct tda1997x_video_std *std) > +{ > + struct tda1997x_state *state = to_state(sd); > + const struct v4l2_bt_timings *bt = &std->timings.bt; > + const struct vhref_values *vh = &std->vhref_values; > + int width, lines; > + u8 reg; > + > + v4l_dbg(1, debug, state->client, "%s %dx%d%c@%dHz\n", __func__, > + bt->width, bt->height, > + bt->interlaced ? 'i' : 'p', state->fps); > + width = bt->width + bt->hfrontporch + bt->hsync + > + bt->hbackporch; > + lines = bt->height + bt->vfrontporch + bt->vsync + > + bt->vbackporch; > + > + /* > + * 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, vh->href_start & MASK_VHREF); > + io_write16(sd, REG_HREF_E, vh->href_end & MASK_VHREF); > + /* vertical reference f1 start/end */ > + io_write16(sd, REG_VREF_F1_S, vh->vref_f1_start & MASK_VHREF); > + io_write(sd, REG_VREF_F1_WIDTH, vh->vref_f1_width); > + /* vertical reference f2 start/end */ > + io_write16(sd, REG_VREF_F2_S, vh->vref_f2_start & MASK_VHREF); > + io_write(sd, REG_VREF_F2_WIDTH, vh->vref_f2_width); > + /* F1/F2 FREF, field polarity */ > + io_write16(sd, REG_FREF_F1_S, (vh->fieldref_f1_start & MASK_VHREF) > + || (vh->fieldPolarity << 8)); > + io_write16(sd, REG_FREF_F2_S, vh->fieldref_f2_start & MASK_VHREF); > + > + return 0; > +} > + > +/* 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/7taps 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 int > +tda1997x_detect_std(struct tda1997x_state *state) > +{ > + 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; > + if (!vper || !hper || !hsper) > + return -ENOLINK; > + > + /* look for matching timings */ > + for (i = 0; i < ARRAY_SIZE(tda1997x_hdmi_modes); i++) { > + const struct tda1997x_video_std *std = &tda1997x_hdmi_modes[i]; > + const struct v4l2_bt_timings *bt = &std->timings.bt; > + int lines, width, _hper, _vper, _hsper; > + int vmin, vmax, hmin, hmax, hsmin, hsmax; > + int hmatch, vmatch, hsmatch; > + > + width = bt->width + bt->hfrontporch + bt->hsync + > + bt->hbackporch; > + lines = bt->height + bt->vfrontporch + bt->vsync + > + bt->vbackporch; Use V4L2_DV_BT_FRAME_WIDTH/HEIGHT defines for this. > + > + _hper = (int)bt->pixelclock / (int)width; > + _vper = _hper / lines; > + _hsper = (int)bt->pixelclock / (int)bt->hsync; > + if (bt->interlaced) > + _vper *= 2; > + /* vper +/- 0.7% */ > + vmin = 993 * (27000000 / _vper) / 1000; > + vmax = 1007 * (27000000 / _vper) / 1000; > + /* hper +/- 0.7% */ > + hmin = 993 * (27000000 / _hper) / 1000; > + hmax = 1007 * (27000000 / _hper) / 1000; > + /* hsper +/- 0.7% */ > + hsmin = 993 * (27000000 / _hsper) / 1000; > + hsmax = 1007 * (27000000 / _hsper) / 1000; > + > + vmatch = ((vper <= vmax) && (vper >= vmin)) ? 1 : 0; > + hmatch = ((hper <= hmax) && (hper >= hmin)) ? 1 : 0; > + if (hmatch && vmatch && hsmatch) { > + v4l_info(state->client, > + "resolution: %dx%d%c@%d (%d/%d/%d)\n", > + bt->width, bt->height, bt->interlaced?'i':'p', > + _vper, vper, hper, hsper); > + state->fps = (int)bt->pixelclock / (width * lines); > + state->std = std; > + return 0; > + } > + } > + > + v4l_err(state->client, "no resolution match for timings: %d/%d/%d\n", > + vper, hper, hsper); > + return -EINVAL; > +} > + > +/* 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. > + */ > +static u8 > +tda1997x_read_activity_status_regs(struct v4l2_subdev *sd) > +{ > + u8 reg, status = 0; > + > + /* Read CLK_A_STATUS register */ > + reg = io_read(sd, REG_CLK_A_STATUS); > + /* when stable_clk_x is set to 1, check active_x bit */ > + if ((reg & MASK_CLK_STABLE) && !(reg & MASK_CLK_ACTIVE)) > + reg &= ~MASK_CLK_STABLE; > + status |= ((reg & MASK_CLK_STABLE) >> 2); > + > + /* Read CLK_B_STATUS register */ > + reg = io_read(sd, REG_CLK_B_STATUS); > + /* when stable_clk_x is set to 1, check active_x bit */ > + if ((reg & MASK_CLK_STABLE) && !(reg & MASK_CLK_ACTIVE)) > + reg &= ~MASK_CLK_STABLE; > + status |= ((reg & MASK_CLK_STABLE) >> 1); > + > + /* Read the SUS_STATUS register */ > + reg = io_read(sd, REG_SUS_STATUS); > + > + /* If state = 5 => TMDS is locked */ > + if ((reg & MASK_SUS_STATUS) == LAST_STATE_REACHED) > + status |= MASK_SUS_STATE; > + else > + status &= ~MASK_SUS_STATE; > + > + return status; > +} > + > +/* parse an infoframe and do some sanity checks on it */ > +static unsigned int > +tda1997x_parse_infoframe(struct tda1997x_state *state, u16 addr) > +{ > + struct v4l2_subdev *sd = &state->sd; > + union hdmi_infoframe frame; > + u8 buffer[40]; > + u8 reg; > + int len, err; > + > + /* read data */ > + len = io_readn(sd, addr, sizeof(buffer), buffer); > + err = hdmi_infoframe_unpack(&frame, buffer); > + if (err) { > + v4l_err(state->client, > + "failed parsing %d byte infoframe: 0x%04x/0x%02x\n", > + len, addr, buffer[0]); > + return err; > + } > + if (debug > 1) > + hdmi_infoframe_log(KERN_INFO, &state->client->dev, &frame); > + switch (frame.any.type) { > + /* Audio InfoFrame: see HDMI spec 8.2.2 */ > + case HDMI_INFOFRAME_TYPE_AUDIO: > + /* sample rate */ > + switch (frame.audio.sample_frequency) { > + case HDMI_AUDIO_SAMPLE_FREQUENCY_32000: > + state->audio_samplerate = 32000; > + break; > + case HDMI_AUDIO_SAMPLE_FREQUENCY_44100: > + state->audio_samplerate = 44100; > + break; > + case HDMI_AUDIO_SAMPLE_FREQUENCY_48000: > + state->audio_samplerate = 48000; > + break; > + case HDMI_AUDIO_SAMPLE_FREQUENCY_88200: > + state->audio_samplerate = 88200; > + break; > + case HDMI_AUDIO_SAMPLE_FREQUENCY_96000: > + state->audio_samplerate = 96000; > + break; > + case HDMI_AUDIO_SAMPLE_FREQUENCY_176400: > + state->audio_samplerate = 176400; > + break; > + case HDMI_AUDIO_SAMPLE_FREQUENCY_192000: > + state->audio_samplerate = 192000; > + break; > + default: > + case HDMI_AUDIO_SAMPLE_FREQUENCY_STREAM: > + break; > + } > + > + /* sample size */ > + switch (frame.audio.sample_size) { > + case HDMI_AUDIO_SAMPLE_SIZE_16: > + state->audio_samplesize = 16; > + break; > + case HDMI_AUDIO_SAMPLE_SIZE_20: > + state->audio_samplesize = 20; > + break; > + case HDMI_AUDIO_SAMPLE_SIZE_24: > + state->audio_samplesize = 24; > + break; > + case HDMI_AUDIO_SAMPLE_SIZE_STREAM: > + default: > + break; > + } > + > + /* Channel Count */ > + state->audio_channels = frame.audio.channels; > + if (frame.audio.channel_allocation && > + frame.audio.channel_allocation != state->audio_ch_alloc) { > + /* use the channel assignment from the infoframe */ > + state->audio_ch_alloc = frame.audio.channel_allocation; > + tda1997x_configure_audout(sd, state->audio_ch_alloc); > + /* reset the audio FIFO */ > + tda1997x_hdmi_info_reset(sd, RESET_AUDIO, false); > + } > + break; > + > + /* Source Product Descriptor information (SPD) */ > + case HDMI_INFOFRAME_TYPE_SPD: > + strncpy(frame.spd.vendor, state->vendor, > + sizeof(frame.spd.vendor)); > + strncpy(frame.spd.product, state->product, > + sizeof(frame.spd.product)); > + v4l_info(state->client, "Source Product Descriptor: %s %s\n", > + state->vendor, state->product); Use hdmi_infoframe_log() for logging infoframes. > + break; > + > + /* Auxiliary Video information (AVI) InfoFrame: see HDMI spec 8.2.1 */ > + case HDMI_INFOFRAME_TYPE_AVI: > + state->colorspace = frame.avi.colorspace; > + state->colorimetry = frame.avi.colorimetry; > + /* > + * If colorimetry not specified, conversion depends on res type: > + * - SDTV: ITU601 for SD (480/576/240/288 line resolution) > + * - HDTV: ITU709 for HD (720/1080 line resolution) > + * - PC: sRGB > + * see HDMI specification section 6.7 > + */ > + if ((state->colorspace == HDMI_COLORSPACE_YUV422 || > + state->colorspace == HDMI_COLORSPACE_YUV444) && > + (state->colorimetry == HDMI_COLORIMETRY_EXTENDED || > + state->colorimetry == HDMI_COLORIMETRY_NONE)) { > + switch (state->timings.bt.height) { > + case 480: > + case 576: > + case 240: > + case 288: > + state->colorimetry = HDMI_COLORIMETRY_ITU_601; > + break; > + case 720: > + case 1080: > + state->colorimetry = HDMI_COLORIMETRY_ITU_709; > + break; > + default: > + state->colorimetry = HDMI_COLORIMETRY_NONE; Missing break. > + } > + } > + v4l_dbg(1, debug, state->client, > + "Colorspace=%d Colorimetry=%d\n", > + state->colorspace, state->colorimetry); > + > + /* configure upsampler: 0=bypass 1=repeatchroma 2=interpolate */ > + reg = io_read(sd, REG_PIX_REPEAT); > + reg &= ~PIX_REPEAT_MASK_UP_SEL; > + if (state->colorspace == HDMI_COLORSPACE_YUV422) > + reg |= (PIX_REPEAT_CHROMA << PIX_REPEAT_SHIFT); > + io_write(sd, REG_PIX_REPEAT, reg); > + > + /* ConfigurePixelRepeater: repeat n-times each pixel */ > + reg = io_read(sd, REG_PIX_REPEAT); > + reg &= ~PIX_REPEAT_MASK_REP; > + reg |= frame.avi.pixel_repeat; > + io_write(sd, REG_PIX_REPEAT, reg); > + > + /* configure the receiver with the new colorspace */ > + tda1997x_configure_conv(sd, state->colorspace, > + state->colorimetry); What I am missing here is handling of the RGB quantization range. An HDMI receiver will typically send full range RGB or limited range YUV to the SoC. The HDMI source can however send full or limited range RGB or limited range YUV (full range YUV is theoretically possible, but nobody does that). For a Full HD receiver the rules when receiving RGB video are as follows: If the EDID supports selectable RGB Quantization Range, then check if the source explicitly sets the RGB quantization range in the AVI InfoFrame and use that value. Otherwise fall back to the default rules: if VIC == 0, then expect full range RGB, otherwise expect limited range RGB. It gets even more complicated with 4k video, but this is full HD only. In addition, you may also want to implement the V4L2_CID_DV_RX_RGB_RANGE control to let userspace override the autodetection. RGB Quantization Range handling is *the* biggest headache for HDMI receivers. If you happen to attend the Embedded Linux Conference Europe in Prague next week, then attend my presentation on HDMI 4k Video on the Wednesday for all the reasons why this is so tricky. > + break; > + default: > + break; > + } > + return 0; > +} > + > +static void tda1997x_irq_sus(struct tda1997x_state *state, u8 *flags) > +{ > + struct v4l2_subdev *sd = &state->sd; > + u8 reg, source; > + > + source = io_read(sd, REG_INT_FLG_CLR_SUS); > + io_write(sd, REG_INT_FLG_CLR_SUS, source); > + v4l_dbg(1, debug, state->client, "%s 0x%02x\n", __func__, source); > + > + if (source & MASK_MPT) { > + /* reset MTP in use flag if set */ > + if (state->mptrw_in_progress) > + state->mptrw_in_progress = 0; > + } > + > + if (source & MASK_SUS_END) { > + /* reset audio FIFO */ > + reg = io_read(sd, REG_HDMI_INFO_RST); > + reg |= MASK_SR_FIFO_FIFO_CTRL; > + io_write(sd, REG_HDMI_INFO_RST, reg); > + reg &= ~MASK_SR_FIFO_FIFO_CTRL; > + io_write(sd, REG_HDMI_INFO_RST, reg); > + > + /* reset HDMI flags */ > + state->hdmi_status = 0; > + } > + > + /* filter FMT interrupt based on SUS state */ > + reg = io_read(sd, REG_SUS_STATUS); > + if (((reg & MASK_SUS_STATUS) != LAST_STATE_REACHED) > + || (source & MASK_MPT)) { > + source &= ~MASK_FMT; > + } > + > + if (source & (MASK_FMT | MASK_SUS_END)) { > + reg = io_read(sd, REG_SUS_STATUS); > + if ((reg & MASK_SUS_STATUS) != LAST_STATE_REACHED) { > + v4l_err(state->client, "BAD SUS STATUS\n"); > + return; > + } > + > + /* There is new activity, the status for HDCP repeater state */ > + state->state_c5_reached = 0; > + > + /* Detect the new resolution */ > + if (!tda1997x_detect_std(state)) > + v4l2_subdev_notify_event(&state->sd, &tda1997x_ev_fmt); > + } > +} > + > +static void tda1997x_irq_ddc(struct tda1997x_state *state, u8 *flags) > +{ > + struct v4l2_subdev *sd = &state->sd; > + u8 source; > + > + source = io_read(sd, REG_INT_FLG_CLR_DDC); > + io_write(sd, REG_INT_FLG_CLR_DDC, source); > + if (source & MASK_EDID_MTP) { > + /* reset MTP in use flag if set */ > + if (state->mptrw_in_progress) > + state->mptrw_in_progress = 0; > + } > +} > + > +static void tda1997x_irq_rate(struct tda1997x_state *state, u8 *flags) > +{ > + struct v4l2_subdev *sd = &state->sd; > + u8 reg, source; > + > + u8 irq_status, last_irq_status; > + > + source = io_read(sd, REG_INT_FLG_CLR_RATE); > + io_write(sd, REG_INT_FLG_CLR_RATE, source); > + > + /* read status regs */ > + last_irq_status = irq_status = tda1997x_read_activity_status_regs(sd); > + > + /* > + * read clock status reg until INT_FLG_CLR_RATE is still 0 > + * after the read to make sure its the last one > + */ > + reg = source; > + while (reg != 0) { > + irq_status = tda1997x_read_activity_status_regs(sd); > + reg = io_read(sd, REG_INT_FLG_CLR_RATE); > + io_write(sd, REG_INT_FLG_CLR_RATE, reg); > + source |= reg; > + } > + > + /* we only pay attention to stability change events */ > + if (source & (MASK_RATE_A_ST | MASK_RATE_B_ST)) { > + int input = (source & MASK_RATE_A_ST)?0:1; > + u8 mask = 1<<input; > + > + /* state change */ > + if ((irq_status & mask) != (state->activity_status & mask)) { > + /* activity lost */ > + if ((irq_status & mask) == 0) { > + v4l_info(state->client, > + "HDMI-%c: Digital Activity Lost\n", > + input+'A'); > + > + /* bypass up/down sampler and pixel repeater */ > + reg = io_read(sd, REG_PIX_REPEAT); > + reg &= ~PIX_REPEAT_MASK_UP_SEL; > + reg &= ~PIX_REPEAT_MASK_REP; > + io_write(sd, REG_PIX_REPEAT, reg); > + > + if (state->chip_revision == 0) > + tda1997x_reset_n1(state); > + > + state->std = NULL; > + state->fps = 0; > + state->input_detect[input] = 0; > + v4l2_subdev_notify_event(sd, &tda1997x_ev_fmt); > + } > + > + /* activity detected */ > + else { > + v4l_info(state->client, > + "HDMI-%c: Digital Activity Detected\n", > + input+'A'); > + state->input_detect[input] = 1; > + } > + > + /* hold onto current state */ > + state->activity_status = (irq_status & mask); > + } > + } > +} > + > +static void tda1997x_irq_info(struct tda1997x_state *state, u8 *flags) > +{ > + struct v4l2_subdev *sd = &state->sd; > + u8 source; > + > + source = io_read(sd, REG_INT_FLG_CLR_INFO); > + io_write(sd, REG_INT_FLG_CLR_INFO, source); > + > + /* Audio infoframe */ > + if (source & MASK_AUD_IF) { > + tda1997x_parse_infoframe(state, AUD_IF); > + source &= ~MASK_AUD_IF; > + } > + > + /* Source Product Descriptor infoframe change */ > + if (source & MASK_SPD_IF) { > + tda1997x_parse_infoframe(state, SPD_IF); > + source &= ~MASK_SPD_IF; > + } > + > + /* Auxiliary Video Information infoframe */ > + if (source & MASK_AVI_IF) { > + tda1997x_parse_infoframe(state, AVI_IF); > + source &= ~MASK_AVI_IF; > + } > +} > + > +static void tda1997x_irq_audio(struct tda1997x_state *state, u8 *flags) > +{ > + struct v4l2_subdev *sd = &state->sd; > + u8 reg, source; > + > + source = io_read(sd, REG_INT_FLG_CLR_AUDIO); > + io_write(sd, REG_INT_FLG_CLR_AUDIO, source); > + > + /* reset audio FIFO on FIFO pointer error or audio mute */ > + if (source & MASK_ERROR_FIFO_PT || > + source & MASK_MUTE_FLG) { > + /* audio reset audio FIFO */ > + reg = io_read(sd, REG_SUS_STATUS); > + if ((reg & MASK_SUS_STATUS) == LAST_STATE_REACHED) { > + reg = io_read(sd, REG_HDMI_INFO_RST); > + reg |= MASK_SR_FIFO_FIFO_CTRL; > + io_write(sd, REG_HDMI_INFO_RST, reg); > + reg &= ~MASK_SR_FIFO_FIFO_CTRL; > + io_write(sd, REG_HDMI_INFO_RST, reg); > + /* reset channel status IT if present */ > + source &= ~(MASK_CH_STATE); > + } > + } > + if (source & MASK_AUDIO_FREQ_FLG) { > + static const int freq[] = { > + 0, 32000, 44100, 48000, 88200, 96000, 176400, 192000 > + }; > + > + reg = io_read(sd, REG_AUDIO_FREQ); > + state->audio_samplerate = freq[reg & 7]; > + v4l_info(state->client, "Audio Frequency Change: %dHz\n", > + state->audio_samplerate); > + } > + if (source & MASK_AUDIO_FLG) { > + reg = io_read(sd, REG_AUDIO_FLAGS); > + if (reg & BIT(AUDCFG_TYPE_DST)) > + state->audio_type = AUDCFG_TYPE_DST; > + if (reg & BIT(AUDCFG_TYPE_OBA)) > + state->audio_type = AUDCFG_TYPE_OBA; > + if (reg & BIT(AUDCFG_TYPE_HBR)) > + state->audio_type = AUDCFG_TYPE_HBR; > + if (reg & BIT(AUDCFG_TYPE_PCM)) > + state->audio_type = AUDCFG_TYPE_PCM; > + v4l_info(state->client, "Audio Type: %s\n", > + audtype_names[state->audio_type]); You generally don't want to use v4l_info in interrupt functions. v4l_dbg is fine, though. > + } > +} > + > +static void tda1997x_irq_hdcp(struct tda1997x_state *state, u8 *flags) > +{ > + struct v4l2_subdev *sd = &state->sd; > + u8 reg, source; > + > + source = io_read(sd, REG_INT_FLG_CLR_HDCP); > + io_write(sd, REG_INT_FLG_CLR_HDCP, source); > + > + /* reset MTP in use flag if set */ > + if (source & MASK_HDCP_MTP) > + state->mptrw_in_progress = 0; > + if (source & MASK_STATE_C5) { > + /* REPEATER: mask AUDIO and IF irqs to avoid IF during auth */ > + reg = io_read(sd, REG_INT_MASK_TOP); > + reg &= ~(INTERRUPT_AUDIO | INTERRUPT_INFO); > + io_write(sd, REG_INT_MASK_TOP, reg); > + *flags &= (INTERRUPT_AUDIO | INTERRUPT_INFO); > + } > +} > + > +static irqreturn_t tda1997x_isr_thread(int irq, void *d) > +{ > + struct tda1997x_state *state = d; > + struct v4l2_subdev *sd = &state->sd; > + u8 flags; > + > + mutex_lock(&state->lock); > + do { > + /* read interrupt flags */ > + flags = io_read(sd, REG_INT_FLG_CLR_TOP); > + if (flags == 0) > + break; > + > + /* SUS interrupt source (Input activity events) */ > + if (flags & INTERRUPT_SUS) > + tda1997x_irq_sus(state, &flags); > + /* DDC interrupt source (Display Data Channel) */ > + else if (flags & INTERRUPT_DDC) > + tda1997x_irq_ddc(state, &flags); > + /* RATE interrupt source (Digital Input activity) */ > + else if (flags & INTERRUPT_RATE) > + tda1997x_irq_rate(state, &flags); > + /* Infoframe change interrupt */ > + else if (flags & INTERRUPT_INFO) > + tda1997x_irq_info(state, &flags); > + /* Audio interrupt source: > + * freq change, DST,OBA,HBR,ASP flags, mute, FIFO err > + */ > + else if (flags & INTERRUPT_AUDIO) > + tda1997x_irq_audio(state, &flags); > + /* HDCP interrupt source (content protection) */ > + if (flags & INTERRUPT_HDCP) > + tda1997x_irq_hdcp(state, &flags); > + } while (flags != 0); > + mutex_unlock(&state->lock); > + > + return IRQ_HANDLED; > +} > + > +static bool tda1997x_check_dv_timings(const struct v4l2_dv_timings *timings, > + void *hdl) > +{ > + const struct tda1997x_video_std *std; > + unsigned int i; > + > + for (i = 0; i < ARRAY_SIZE(tda1997x_hdmi_modes); i++) { > + std = &tda1997x_hdmi_modes[i]; > + if (v4l2_match_dv_timings(timings, &std->timings, 0, false)) > + return true; > + } > + > + return false; > +} > + > +/* ----------------------------------------------------------------------------- > + * v4l2_subdev_video_ops > + */ > + > +static int > +tda1997x_g_input_status(struct v4l2_subdev *sd, u32 *status) > +{ > + struct tda1997x_state *state = to_state(sd); > + > + mutex_lock(&state->lock); > + if (state->std) > + *status = 0; > + else > + *status |= V4L2_IN_ST_NO_SIGNAL; > + mutex_unlock(&state->lock); > + > + return 0; > +}; > + > +static int tda1997x_s_dv_timings(struct v4l2_subdev *sd, > + struct v4l2_dv_timings *timings) > +{ > + struct tda1997x_state *state = to_state(sd); > + int ret; > + > + v4l_dbg(1, debug, state->client, "%s\n", __func__); > + if (!timings) > + return -EINVAL; > + > + if (v4l2_match_dv_timings(&state->timings, timings, 0, false)) > + return 0; /* no changes */ > + > + if (!v4l2_valid_dv_timings(timings, &tda1997x_dv_timings_cap, > + tda1997x_check_dv_timings, state)) > + return -ERANGE; > + > + mutex_lock(&state->lock); > + state->timings = *timings; > + /* setup frame detection window and VHREF timing generator */ > + ret = tda1997x_configure_input_resolution(sd, state->std); > + if (ret) > + goto error; > + ret = tda1997x_configure_conv(sd, state->colorspace, > + state->colorimetry); > + if (ret) > + goto error; > + mutex_unlock(&state->lock); > + > + return 0; > + > +error: > + mutex_unlock(&state->lock); > + return ret; > +} > + > +static int tda1997x_g_dv_timings(struct v4l2_subdev *sd, > + struct v4l2_dv_timings *timings) > +{ > + struct tda1997x_state *state = to_state(sd); > + > + v4l_dbg(1, debug, state->client, "%s\n", __func__); > + if (!timings) > + return -EINVAL; > + > + mutex_lock(&state->lock); > + *timings = state->timings; > + mutex_unlock(&state->lock); > + > + return 0; > +} > + > +static int tda1997x_query_dv_timings(struct v4l2_subdev *sd, > + struct v4l2_dv_timings *timings) > +{ > + struct tda1997x_state *state = to_state(sd); > + int ret; > + > + v4l_dbg(1, debug, state->client, "%s\n", __func__); > + if (!timings) > + return -EINVAL; > + > + memset(timings, 0, sizeof(struct v4l2_dv_timings)); > + mutex_lock(&state->lock); > + ret = tda1997x_detect_std(state); > + if (ret) > + goto error; > + *timings = state->std->timings; > + mutex_unlock(&state->lock); > + return 0; > + > +error: > + mutex_unlock(&state->lock); > + return ret; This can be simplified: ret = tda1997x_detect_std(state); if (!ret) *timings = state->std->timings; mutex_unlock(&state->lock); return ret; > +} > + > +static int tda1997x_s_stream(struct v4l2_subdev *sd, int enable) > +{ > + struct tda1997x_state *state = to_state(sd); > + > + v4l_dbg(1, debug, state->client, "%s %d\n", __func__, enable); > + mutex_lock(&state->lock); > + if (!state->std) > + v4l_dbg(1, debug, state->client, "Invalid HDMI signal\n"); > + mutex_unlock(&state->lock); > + > + return 0; > +} > + > +static const struct v4l2_subdev_video_ops tda1997x_video_ops = { > + .g_input_status = tda1997x_g_input_status, > + .s_dv_timings = tda1997x_s_dv_timings, > + .g_dv_timings = tda1997x_g_dv_timings, > + .query_dv_timings = tda1997x_query_dv_timings, > + .s_stream = tda1997x_s_stream, > +}; > + > + > +/* ----------------------------------------------------------------------------- > + * v4l2_subdev_pad_ops > + */ > + > +static int tda1997x_enum_mbus_code(struct v4l2_subdev *sd, > + struct v4l2_subdev_pad_config *cfg, > + struct v4l2_subdev_mbus_code_enum *code) > +{ > + struct tda1997x_state *state = to_state(sd); > + > + if (code->index > 0) > + return -EINVAL; > + > + code->code = state->code; > + > + return 0; > +} > + > +static int tda1997x_fill_format(struct tda1997x_state *state, > + struct v4l2_mbus_framefmt *format) > +{ > + v4l_dbg(1, debug, state->client, "%s\n", __func__); > + if (!state->std) > + return -EINVAL; > + memset(format, 0, sizeof(*format)); > + > + format->width = state->std->timings.bt.width; > + format->height = state->std->timings.bt.height; > + format->field = V4L2_FIELD_NONE; > + format->colorspace = V4L2_COLORSPACE_SRGB; > + if (state->timings.bt.flags & V4L2_DV_FL_IS_CE_VIDEO) > + format->colorspace = (state->std->timings.bt.height <= 576) ? > + V4L2_COLORSPACE_SMPTE170M : V4L2_COLORSPACE_REC709; > + > + return 0; > +} > + > +static int tda1997x_get_pad_format(struct v4l2_subdev *sd, > + struct v4l2_subdev_pad_config *cfg, > + struct v4l2_subdev_format *format) > +{ > + struct tda1997x_state *state = to_state(sd); > + > + v4l_dbg(1, debug, state->client, "%s\n", __func__); > + if (format->pad != TDA1997X_PAD_SOURCE) > + return -EINVAL; > + > + tda1997x_fill_format(state, &format->format); > + > + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { > + struct v4l2_mbus_framefmt *fmt; > + > + fmt = v4l2_subdev_get_try_format(sd, cfg, format->pad); > + format->format.code = format->format.code; > + } else > + format->format.code = state->code; > + > + return 0; > +} > + > +static int tda1997x_set_pad_format(struct v4l2_subdev *sd, > + struct v4l2_subdev_pad_config *cfg, > + struct v4l2_subdev_format *format) > +{ > + struct v4l2_mbus_framefmt *fmt; > + > + if (format->pad != TDA1997X_PAD_SOURCE) > + return -EINVAL; > + > + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) > + return tda1997x_get_pad_format(sd, cfg, format); > + > + fmt = v4l2_subdev_get_try_format(sd, cfg, format->pad); > + *fmt = format->format; > + > + return 0; > +} > + > +static int tda1997x_get_edid(struct v4l2_subdev *sd, struct v4l2_edid *edid) > +{ > + struct tda1997x_state *state = to_state(sd); > + > + v4l_dbg(1, debug, state->client, "%s pad=%d\n", __func__, edid->pad); > + memset(edid->reserved, 0, sizeof(edid->reserved)); > + > + if (!state->edid.present) > + return -ENODATA; > + > + if (edid->start_block == 0 && edid->blocks == 0) { > + edid->blocks = state->edid.blocks; > + return 0; > + } > + > + if (edid->start_block >= state->edid.blocks) > + return -EINVAL; > + > + if (edid->start_block + edid->blocks > state->edid.blocks) > + edid->blocks = state->edid.blocks - edid->start_block; > + > + memcpy(edid->edid, state->edid.edid + edid->start_block * 128, > + edid->blocks * 128); > + > + return 0; > +} > + > +static int tda1997x_set_edid(struct v4l2_subdev *sd, struct v4l2_edid *edid) > +{ > + struct tda1997x_state *state = to_state(sd); > + int i; > + > + v4l_dbg(1, debug, state->client, "%s pad=%d\n", __func__, edid->pad); > + memset(edid->reserved, 0, sizeof(edid->reserved)); > + > + if (edid->start_block != 0) > + return -EINVAL; > + > + if (edid->blocks == 0) { > + state->edid.blocks = 0; > + state->edid.present = 0; > + tda1997x_manual_hpd(&state->sd, HPD_LOW_BP); > + return 0; > + } > + > + if (edid->blocks > 2) { > + edid->blocks = 2; > + return -E2BIG; > + } > + > + /* write base EDID */ > + for (i = 0; i < 128; i++) > + io_write(sd, REG_EDID_IN_BYTE0 + i, edid->edid[i]); > + > + /* write CEA Extension */ > + for (i = 0; i < 128; i++) > + io_write(sd, REG_EDID_IN_BYTE128 + i, edid->edid[i+128]); > + Before updating the EDID pull the HPD low. Afterwards pull it up again. The minimum time the HPD should remain low is 100 ms. > + return 0; > +} > + > +static int tda1997x_get_dv_timings_cap(struct v4l2_subdev *sd, > + struct v4l2_dv_timings_cap *cap) > +{ > + *cap = tda1997x_dv_timings_cap; > + return 0; > +} > + > +static int tda1997x_enum_dv_timings(struct v4l2_subdev *sd, > + struct v4l2_enum_dv_timings *timings) > +{ > + struct tda1997x_state *state = to_state(sd); > + > + return v4l2_enum_dv_timings_cap(timings, &tda1997x_dv_timings_cap, > + tda1997x_check_dv_timings, state); > +} > + > +static const struct v4l2_subdev_pad_ops tda1997x_pad_ops = { > + .enum_mbus_code = tda1997x_enum_mbus_code, > + .get_fmt = tda1997x_get_pad_format, > + .set_fmt = tda1997x_set_pad_format, > + .get_edid = tda1997x_get_edid, > + .set_edid = tda1997x_set_edid, > + .dv_timings_cap = tda1997x_get_dv_timings_cap, > + .enum_dv_timings = tda1997x_enum_dv_timings, > +}; > + > +/* ----------------------------------------------------------------------------- > + * v4l2_subdev_core_ops > + */ > + > +static int tda1997x_log_status(struct v4l2_subdev *sd) > +{ > + struct tda1997x_state *state = to_state(sd); > + const struct v4l2_dv_timings *timings = &state->timings; > + > + v4l2_info(sd, "-----Signal status-----\n"); > + if (!timings) { timings can never be NULL. > + v4l2_info(sd, "no signal\n"); > + return 0; > + } > + v4l2_info(sd, "resolution: %dx%d%c@%dHz\n", > + timings->bt.width, timings->bt.height, > + timings->bt.interlaced ? 'i' : 'p', > + state->fps); > + v4l2_print_dv_timings(sd->name, "Detected format: ", > + timings, true); > + v4l2_info(sd, "colorspace: %d\n", state->colorspace); > + v4l2_info(sd, "colorimetry: %d\n", state->colorimetry); > + if (state->audio_channels) > + v4l2_info(sd, "audio: %dch %dHz\n", state->audio_channels, > + state->audio_samplerate); > + else > + v4l2_info(sd, "audio: none\n"); > + v4l2_info(sd, "vendor: %s\n", state->vendor); > + v4l2_info(sd, "product: %s\n", state->product); If at all possible you should log the received InfoFrames here (hdmi_infoframe_log). Also whether an EDID is loaded or not and the HPD state. If the hardware supports 5V detection, then you should log that too. In that case also implement support for the V4L2_CID_DV_RX_POWER_PRESENT control. Also any information on the signal detection (clock lock, sync lock(s), whatever). This all helps enormously when debugging problems. It's important to spend some time on this function. The adv7604.c source might be a good place to look for inspiration. > + > + return 0; > +} > + > +static int tda1997x_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 tda1997x_core_ops = { > + .log_status = tda1997x_log_status, > + .subscribe_event = tda1997x_subscribe_event, > + .unsubscribe_event = v4l2_event_subdev_unsubscribe, > +}; > + > +/* ----------------------------------------------------------------------------- > + * v4l2_subdev_ops > + */ > + > +static const struct v4l2_subdev_ops tda1997x_subdev_ops = { > + .core = &tda1997x_core_ops, > + .video = &tda1997x_video_ops, > + .pad = &tda1997x_pad_ops, > +}; > + > +static int tda1997x_core_init(struct v4l2_subdev *sd) > +{ > + struct tda1997x_state *state = to_state(sd); > + struct tda1997x_platform_data *pdata = &state->pdata; > + u8 reg; > + int i; > + > + /* disable HPD */ > + io_write(sd, REG_HPD_AUTO_CTRL, HPD_AUTO_HPD_UNSEL); > + if (state->chip_revision == 0) { > + io_write(sd, REG_MAN_SUS_HDMI_SEL, MAN_DIS_HDCP | MAN_RST_HDCP); > + io_write(sd, REG_CGU_DBG_SEL, 1 << CGU_DBG_CLK_SEL_SHIFT); > + } > + > + /* reset infoframe at end of start-up-sequencer */ > + io_write(sd, REG_SUS_SET_RGB2, 0x06); > + io_write(sd, REG_SUS_SET_RGB3, 0x06); > + > + /* Enable TMDS pull-ups */ > + io_write(sd, REG_RT_MAN_CTRL, RT_MAN_CTRL_RT | > + RT_MAN_CTRL_RT_B | RT_MAN_CTRL_RT_A); > + > + /* enable sync measurement timing */ > + tda1997x_cec_write(sd, REG_PWR_CONTROL & 0xff, 0x04); > + /* adjust CEC clock divider */ > + tda1997x_cec_write(sd, REG_OSC_DIVIDER & 0xff, 0x03); > + tda1997x_cec_write(sd, REG_EN_OSC_PERIOD_LSB & 0xff, 0xa0); > + io_write(sd, REG_TIMER_D, 0x54); > + /* enable power switch */ > + reg = tda1997x_cec_read(sd, REG_CONTROL & 0xff); > + reg |= 0x20; > + tda1997x_cec_write(sd, REG_CONTROL & 0xff, reg); > + mdelay(50); > + > + /* read the chip version */ > + reg = io_read(sd, REG_VERSION); > + /* get the chip configuration */ > + reg = io_read(sd, REG_CMTP_REG10); > + > + /* enable interrupts we care about */ > + io_write(sd, REG_INT_MASK_TOP, > + INTERRUPT_HDCP | INTERRUPT_AUDIO | INTERRUPT_INFO | > + INTERRUPT_RATE | INTERRUPT_SUS); > + /* config_mtp,fmt,sus_end,sus_st */ > + io_write(sd, REG_INT_MASK_SUS, MASK_MPT | MASK_FMT | MASK_SUS_END); > + /* rate stability change for inputs A/B */ > + io_write(sd, REG_INT_MASK_RATE, MASK_RATE_B_ST | MASK_RATE_A_ST); > + /* aud,spd,avi*/ > + io_write(sd, REG_INT_MASK_INFO, > + MASK_AUD_IF | MASK_SPD_IF | MASK_AVI_IF); > + /* audio_freq,audio_flg,mute_flg,fifo_err */ > + io_write(sd, REG_INT_MASK_AUDIO, > + MASK_AUDIO_FREQ_FLG | MASK_AUDIO_FLG | MASK_MUTE_FLG | > + MASK_ERROR_FIFO_PT); > + /* HDCP C5 state reached */ > + io_write(sd, REG_INT_MASK_HDCP, MASK_STATE_C5); > + /* don't care about AFE/DDC/MODE */ > + io_write(sd, REG_INT_MASK_AFE, 0); > + io_write(sd, REG_INT_MASK_DDC, 0); > + io_write(sd, REG_INT_MASK_MODE, 0); > + > + /* clear all interrupts */ > + io_write(sd, REG_INT_FLG_CLR_TOP, 0xff); > + io_write(sd, REG_INT_FLG_CLR_SUS, 0xff); > + io_write(sd, REG_INT_FLG_CLR_DDC, 0xff); > + io_write(sd, REG_INT_FLG_CLR_RATE, 0xff); > + io_write(sd, REG_INT_FLG_CLR_MODE, 0xff); > + io_write(sd, REG_INT_FLG_CLR_INFO, 0xff); > + io_write(sd, REG_INT_FLG_CLR_AUDIO, 0xff); > + io_write(sd, REG_INT_FLG_CLR_HDCP, 0xff); > + io_write(sd, REG_INT_FLG_CLR_AFE, 0xff); > + > + /* init TMDS equalizer */ > + if (state->chip_revision == 0) > + io_write(sd, REG_CGU_DBG_SEL, 1 << CGU_DBG_CLK_SEL_SHIFT); > + io_write24(sd, REG_CLK_MIN_RATE, CLK_MIN_RATE); > + io_write24(sd, REG_CLK_MAX_RATE, CLK_MAX_RATE); > + if (state->chip_revision == 0) > + io_write(sd, REG_WDL_CFG, WDL_CFG_VAL); > + /* DC filter */ > + io_write(sd, REG_DEEP_COLOR_CTRL, DC_FILTER_VAL); > + /* disable test pattern */ > + io_write(sd, REG_SVC_MODE, 0x00); > + /* update HDMI INFO CTRL */ > + io_write(sd, REG_INFO_CTRL, 0xff); > + /* write HDMI INFO EXCEED value */ > + io_write(sd, REG_INFO_EXCEED, 3); > + > + if (state->chip_revision == 0) > + tda1997x_reset_n1(state); > + > + /* > + * No HDCP acknowledge when HDCP is disabled > + * and reset SUS to force format detection > + */ > + tda1997x_hdmi_info_reset(sd, NACK_HDCP, true); > + > + /* Set HPD low */ > + tda1997x_manual_hpd(sd, HPD_LOW_BP); > + > + /* Configure receiver capabilities */ > + io_write(sd, REG_HDCP_BCAPS, HDCP_HDMI | HDCP_FAST_REAUTH); > + > + /* Configure HDMI: Auto HDCP mode, packet controlled mute */ > + reg = HDMI_CTRL_MUTE_AUTO << HDMI_CTRL_MUTE_SHIFT; > + reg |= HDMI_CTRL_HDCP_AUTO << HDMI_CTRL_HDCP_SHIFT; > + io_write(sd, REG_HDMI_CTRL, reg); > + > + /* reset start-up-sequencer to force format detection */ > + tda1997x_hdmi_info_reset(sd, 0, true); > + > + /* Set HPD high */ > + tda1997x_manual_hpd(sd, HPD_HIGH_OTHER); > + tda1997x_manual_hpd(sd, HPD_HIGH_BP); How can you set the HPD high if there is no EDID? No EDID, no HPD. > + > + /* disable matrix conversion */ > + reg = io_read(sd, REG_VDP_CTRL); > + reg |= VDP_CTRL_MATRIX_BP; > + io_write(sd, REG_VDP_CTRL, reg); > + > + /* set video output mode */ > + tda1997x_configure_vidout(state); > + > + /* configure video output port */ > + for (i = 0; i < 9; i++) { > + v4l_dbg(1, debug, state->client, "vidout_cfg[%d]=0x%02x\n", i, > + pdata->vidout_port_cfg[i]); > + io_write(sd, REG_VP35_32_CTRL + i, pdata->vidout_port_cfg[i]); > + } > + > + /* configure audio output port */ > + tda1997x_configure_audout(sd, 0); > + > + /* configure audio clock freq */ > + switch (pdata->audout_mclk_fs) { > + case 512: > + reg = AUDIO_CLOCK_SEL_512FS; > + break; > + case 256: > + reg = AUDIO_CLOCK_SEL_256FS; > + break; > + case 128: > + reg = AUDIO_CLOCK_SEL_128FS; > + break; > + case 64: > + reg = AUDIO_CLOCK_SEL_64FS; > + break; > + case 32: > + reg = AUDIO_CLOCK_SEL_32FS; > + break; > + default: > + reg = AUDIO_CLOCK_SEL_16FS; > + break; > + } > + io_write(sd, REG_AUDIO_CLOCK, reg); > + > + /* reset advanced infoframes (ISRC1/ISRC2/ACP) */ > + tda1997x_hdmi_info_reset(sd, RESET_AI, false); > + /* reset infoframe */ > + tda1997x_hdmi_info_reset(sd, RESET_IF, false); > + /* reset audio infoframes */ > + tda1997x_hdmi_info_reset(sd, RESET_AUDIO, false); > + /* reset gamut */ > + tda1997x_hdmi_info_reset(sd, RESET_GAMUT, false); > + > + /* get initial HDMI status */ > + state->hdmi_status = io_read(sd, REG_HDMI_FLAGS); > + > + return 0; > +} > + > +static int tda1997x_set_power(struct tda1997x_state *state, bool on) > +{ > + int ret = 0; > + > + if (on) { > + ret = regulator_bulk_enable(TDA1997X_NUM_SUPPLIES, > + state->supplies); > + msleep(300); > + } > + > + ret = regulator_bulk_disable(TDA1997X_NUM_SUPPLIES, > + state->supplies); > + return ret; > +} > + > +static const struct i2c_device_id tda1997x_i2c_id[] = { > + {"tda19971", (kernel_ulong_t)&tda1997x_chip_info[TDA19971]}, > + {"tda19973", (kernel_ulong_t)&tda1997x_chip_info[TDA19973]}, > + { }, > +}; > +MODULE_DEVICE_TABLE(i2c, tda1997x_i2c_id); > + > +static const struct of_device_id tda1997x_of_id[] __maybe_unused = { > + { .compatible = "nxp,tda19971", .data = &tda1997x_chip_info[TDA19971] }, > + { .compatible = "nxp,tda19973", .data = &tda1997x_chip_info[TDA19973] }, > + { }, > +}; > +MODULE_DEVICE_TABLE(of, tda1997x_of_id); > + > +static int tda1997x_parse_dt(struct tda1997x_state *state) > +{ > + struct tda1997x_platform_data *pdata = &state->pdata; > + struct v4l2_fwnode_endpoint bus_cfg; > + struct device_node *ep; > + struct device_node *np; > + unsigned int flags; > + const char *str; > + int ret; > + u32 v; > + > + /* > + * setup default values: > + * - HREF: active high from start to end of row > + * - VS: Vertical Sync active high at beginning of frame > + * - DE: Active high when data valid > + * - A_CLK: 128*Fs > + */ > + pdata->vidout_sel_hs = HS_HREF_SEL_HREF_VHREF; > + pdata->vidout_sel_vs = VS_VREF_SEL_VREF_HDMI; > + pdata->vidout_sel_de = DE_FREF_SEL_DE_VHREF; > + > + np = state->client->dev.of_node; > + ep = of_graph_get_next_endpoint(np, NULL); > + if (!ep) > + return -EINVAL; > + > + ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep), &bus_cfg); > + if (ret) { > + of_node_put(ep); > + return ret; > + } > + of_node_put(ep); > + > + /* polarity of HS/VS/DE */ > + flags = bus_cfg.bus.parallel.flags; > + if (flags & V4L2_MBUS_HSYNC_ACTIVE_LOW) > + pdata->vidout_inv_hs = 1; > + if (flags & V4L2_MBUS_VSYNC_ACTIVE_LOW) > + pdata->vidout_inv_vs = 1; > + if (flags & V4L2_MBUS_DATA_ACTIVE_LOW) > + pdata->vidout_inv_de = 1; > + pdata->vidout_bus_width = bus_cfg.bus.parallel.bus_width; > + > + /* video output port config */ > + ret = of_property_count_u32_elems(np, "nxp,vidout-portcfg"); > + if (ret > 0) { > + u32 reg, val, i; > + > + for (i = 0; i < ret / 2 && i < 9; i++) { > + of_property_read_u32_index(np, "nxp,vidout-portcfg", > + i * 2, ®); > + of_property_read_u32_index(np, "nxp,vidout-portcfg", > + i * 2 + 1, &val); > + if (reg < 9) > + pdata->vidout_port_cfg[reg] = val; > + } > + } else { > + v4l_err(state->client, "nxp,vidout-portcfg missing\n"); > + return -EINVAL; > + } > + > + /* default to channel layout dictated by packet header */ > + pdata->audout_layoutauto = true; > + > + pdata->audout_format = AUDFMT_TYPE_DISABLED; > + if (!of_property_read_string(np, "nxp,audout-format", &str)) { > + if (strcmp(str, "i2s") == 0) > + pdata->audout_format = AUDFMT_TYPE_I2S; > + else if (strcmp(str, "spdif") == 0) > + pdata->audout_format = AUDFMT_TYPE_SPDIF; > + else { > + v4l_err(state->client, "nxp,audout-format invalid\n"); > + return -EINVAL; > + } > + if (!of_property_read_u32(np, "nxp,audout-layout", &v)) { > + switch (v) { > + case 0: > + case 1: > + break; > + default: > + v4l_err(state->client, > + "nxp,audout-layout invalid\n"); > + return -EINVAL; > + } > + pdata->audout_layout = v; > + } > + if (!of_property_read_u32(np, "nxp,audout-width", &v)) { > + switch (v) { > + case 16: > + case 32: > + break; > + default: > + v4l_err(state->client, > + "nxp,audout-width invalid\n"); > + return -EINVAL; > + } > + pdata->audout_width = v; > + } > + if (!of_property_read_u32(np, "nxp,audout-mclk-fs", &v)) { > + switch (v) { > + case 512: > + case 256: > + case 128: > + case 64: > + case 32: > + case 16: > + break; > + default: > + v4l_err(state->client, > + "nxp,audout-mclk-fs invalid\n"); > + return -EINVAL; > + } > + pdata->audout_mclk_fs = v; > + } > + } > + > + return 0; > +} > + > +static int tda1997x_get_regulators(struct tda1997x_state *state) > +{ > + int i; > + > + for (i = 0; i < TDA1997X_NUM_SUPPLIES; i++) > + state->supplies[i].supply = tda1997x_supply_name[i]; > + > + return devm_regulator_bulk_get(&state->client->dev, > + TDA1997X_NUM_SUPPLIES, > + state->supplies); > +} > + > +static int tda1997x_identify_module(struct tda1997x_state *state) > +{ > + struct v4l2_subdev *sd = &state->sd; > + enum tda1997x_type type; > + u8 reg; > + > + /* Read chip configuration*/ > + reg = io_read(sd, REG_CMTP_REG10); > + state->tmdsb_clk = (reg >> 6) & 0x01; /* use tmds clock B_inv for B */ > + state->tmdsb_soc = (reg >> 5) & 0x01; /* tmds of input B */ > + state->port_30bit = (reg >> 2) & 0x03; /* 30bit vs 24bit */ > + state->output_2p5 = (reg >> 1) & 0x01; /* output supply 2.5v */ > + switch ((reg >> 4) & 0x03) { > + case 0x00: > + type = TDA19971; > + break; > + case 0x02: > + case 0x03: > + type = TDA19973; > + break; > + default: > + dev_err(&state->client->dev, "unsupported chip ID\n"); > + return -EIO; > + } > + if (state->info->type != type) { > + dev_err(&state->client->dev, "chip id mismatch\n"); > + return -EIO; > + } > + > + /* read chip revision */ > + state->chip_revision = io_read(sd, REG_CMTP_REG11); > + > + return 0; > +} > + > +static const struct media_entity_operations tda1997x_media_ops = { > + .link_validate = v4l2_subdev_link_validate, > +}; > + > + > +/* ----------------------------------------------------------------------------- > + * HDMI Audio Codec > + */ > + > +/* refine sample-rate based on HDMI source */ > +static int tda1997x_pcm_startup(struct snd_pcm_substream *substream, > + struct snd_soc_dai *dai) > +{ > + struct tda1997x_state *state = snd_soc_dai_get_drvdata(dai); > + struct snd_soc_codec *codec = dai->codec; > + struct snd_pcm_runtime *rtd = substream->runtime; > + int rate, err; > + > + rate = state->audio_samplerate; > + err = snd_pcm_hw_constraint_minmax(rtd, SNDRV_PCM_HW_PARAM_RATE, > + rate, rate); > + if (err < 0) { > + dev_err(codec->dev, "failed to constrain samplerate to %dHz\n", > + rate); > + return err; > + } > + dev_info(codec->dev, "set samplerate constraint to %dHz\n", rate); > + > + return 0; > +} > + > +static const struct snd_soc_dai_ops tda1997x_dai_ops = { > + .startup = tda1997x_pcm_startup, > +}; > + > +static struct snd_soc_dai_driver tda1997x_audio_dai = { > + .name = "tda1997x", > + .capture = { > + .stream_name = "Capture", > + .channels_min = 2, > + .channels_max = 8, > + .rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | > + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | > + SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 | > + SNDRV_PCM_RATE_192000, > + }, > + .ops = &tda1997x_dai_ops, > +}; > + > +static int tda1997x_codec_probe(struct snd_soc_codec *codec) > +{ > + return 0; > +} > + > +static int tda1997x_codec_remove(struct snd_soc_codec *codec) > +{ > + return 0; > +} > + > +static struct snd_soc_codec_driver tda1997x_codec_driver = { > + .probe = tda1997x_codec_probe, > + .remove = tda1997x_codec_remove, > + .reg_word_size = sizeof(u16), > +}; > + > +static int tda1997x_probe(struct i2c_client *client, > + const struct i2c_device_id *id) > +{ > + struct tda1997x_state *state; > + struct tda1997x_platform_data *pdata; > + struct v4l2_subdev *sd; > + int ret; > + > + /* Check if the adapter supports the needed features */ > + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) > + return -EIO; > + > + state = kzalloc(sizeof(struct tda1997x_state), GFP_KERNEL); > + if (!state) > + return -ENOMEM; > + > + state->client = client; > + pdata = &state->pdata; > + if (IS_ENABLED(CONFIG_OF) && client->dev.of_node) { > + const struct of_device_id *oid; > + > + oid = of_match_node(tda1997x_of_id, client->dev.of_node); > + state->info = oid->data; > + > + ret = tda1997x_parse_dt(state); > + if (ret < 0) { > + v4l_err(client, "DT parsing error\n"); > + goto err_free_state; > + } > + } else if (client->dev.platform_data) { > + struct tda1997x_platform_data *pdata = > + client->dev.platform_data; > + state->info = > + (const struct tda1997x_chip_info *)id->driver_data; > + state->pdata = *pdata; > + } else { > + v4l_err(client, "No platform data\n"); > + ret = -ENODEV; > + goto err_free_state; > + } > + > + ret = tda1997x_get_regulators(state); > + if (ret) > + goto err_free_state; > + > + ret = tda1997x_set_power(state, 1); > + if (ret) > + goto err_free_state; > + > + mutex_init(&state->page_lock); > + mutex_init(&state->lock); > + state->page = 0xff; > + > + /* set video format based on chip and bus width */ > + ret = tda1997x_identify_module(state); > + if (ret) > + goto err_free_mutex; > + > + /* initialize subdev */ > + sd = &state->sd; > + v4l2_i2c_subdev_init(sd, client, &tda1997x_subdev_ops); > + snprintf(sd->name, sizeof(sd->name), "%s %d-%04x", > + id->name, i2c_adapter_id(client->adapter), > + client->addr); > + sd->flags = V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; > + sd->entity.ops = &tda1997x_media_ops; > + > + switch (state->info->type) { > + case TDA19973: > + switch (pdata->vidout_bus_width) { > + case 36: /* 36bit RGB (could also be 36bit YUV) */ > + state->code = MEDIA_BUS_FMT_YUV12_1X36; > + break; > + case 24: /* 24bit BT656 (YUV422 semi-planar: 1-cycle) */ > + state->code = MEDIA_BUS_FMT_UYVY12_1X24; > + break; > + case 12: /* 12bit BT656 (2-cycle) */ > + state->code = MEDIA_BUS_FMT_UYVY12_2X12; > + break; > + } > + break; > + case TDA19971: > + switch (pdata->vidout_bus_width) { > + case 24: /* 24bit YUV (could also be RGB or YUV422) */ > + state->code = MEDIA_BUS_FMT_YUV8_1X24; > + break; > + case 20: /* 20bit YUV422 */ > + state->code = MEDIA_BUS_FMT_UYVY10_1X20; > + break; > + case 16: /* 16bit BT656 (YUV422 semi-planar: 1-cycle) */ > + state->code = MEDIA_BUS_FMT_UYVY8_1X16; > + break; > + case 12: /* 12bit BT656 (2-cycle) */ > + state->code = MEDIA_BUS_FMT_UYVY12_2X12; > + break; > + case 10: /* 10bit BT656 (2-cycle) */ > + state->code = MEDIA_BUS_FMT_UYVY10_2X10; > + break; > + case 8: /* 8bit BT656 (2-cycle) */ > + state->code = MEDIA_BUS_FMT_UYVY8_2X8; > + break; > + } > + } > + switch (state->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->bus_type = V4L2_MBUS_PARALLEL; > + state->vid_fmt = OF_FMT_444; > + break; > + case MEDIA_BUS_FMT_UYVY12_1X24: > + case MEDIA_BUS_FMT_UYVY8_1X16: > + state->bus_type = V4L2_MBUS_BT656; > + 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->bus_type = V4L2_MBUS_BT656; > + state->vid_fmt = OF_FMT_422_CCIR; > + break; > + default: > + v4l_err(state->client, "incompatible code (%x)\n", state->code); > + ret = -EINVAL; > + goto err_free_mutex; > + } > + state->colorspace = HDMI_COLORSPACE_RGB; > + state->colorimetry = HDMI_COLORIMETRY_NONE; > + > + /* disable/reset HDCP to get correct I2C access to Rx HDMI */ > + io_write(sd, REG_MAN_SUS_HDMI_SEL, MAN_RST_HDCP | MAN_DIS_HDCP); > + > + /* > + * if N2 version, reset compdel_bp as it may generate some small pixel > + * shifts in case of embedded sync/or delay lower than 4 > + */ > + if (state->chip_revision != 0) { > + io_write(sd, REG_MAN_SUS_HDMI_SEL, 0x00); > + io_write(sd, REG_VDP_CTRL, 0x1f); > + } > + > + v4l_info(client, "NXP %s N%d detected\n", state->info->name, > + state->chip_revision + 1); > + v4l_info(client, "video: %dbit %s %s MBUS_FMT=0x%x\n", > + pdata->vidout_bus_width, > + (state->bus_type == V4L2_MBUS_PARALLEL) ? "parallel" : "BT656", > + vidfmt_names[state->vid_fmt & OF_FMT_MASK], > + state->code); > + if (pdata->audout_format) { > + v4l_info(client, "audio: %dch %s layout%d sysclk=%d*fs\n", > + pdata->audout_layout ? 2 : 8, > + audfmt_names[pdata->audout_format], > + pdata->audout_layout, > + pdata->audout_mclk_fs); > + } > + > + ret = 0x34 + ((io_read(sd, REG_SLAVE_ADDR)>>4) & 0x03); > + state->client_cec = i2c_new_dummy(client->adapter, ret); > + v4l_info(client, "CEC slave address 0x%02x\n", ret); > + > + ret = tda1997x_core_init(sd); Unless I missed it, I don't think state->timings has been initialized to something valid. During probe the hdmi receiver has to be initialized to something. The API expects that. Usually VGA or 720p60 or 1080p60 is chosen for this. > + if (ret) > + goto err_free_mutex; > + > + /* initialize source pads */ > + state->pads[TDA1997X_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; > + ret = media_entity_pads_init(&sd->entity, TDA1997X_NUM_PADS, > + state->pads); > + if (ret) { > + v4l_err(client, "failed entity_init: %d", ret); > + goto err_free_mutex; > + } > + > + ret = v4l2_async_register_subdev(sd); > + if (ret) > + goto err_free_media; > + > + /* register audio DAI */ > + if (pdata->audout_format) { > + u64 formats; > + > + if (pdata->audout_width == 32) > + formats = SNDRV_PCM_FMTBIT_S32_LE; > + else > + formats = SNDRV_PCM_FMTBIT_S16_LE; > + tda1997x_audio_dai.capture.formats = formats; > + ret = snd_soc_register_codec(&state->client->dev, > + &tda1997x_codec_driver, > + &tda1997x_audio_dai, 1); > + if (ret) { > + dev_err(&client->dev, "register audio codec failed\n"); > + goto err_free_media; > + } > + dev_set_drvdata(&state->client->dev, state); > + v4l_info(state->client, "registered audio codec\n"); > + } > + > + /* request irq */ > + ret = devm_request_threaded_irq(&client->dev, client->irq, > + NULL, tda1997x_isr_thread, > + IRQF_TRIGGER_LOW | IRQF_ONESHOT, > + KBUILD_MODNAME, state); > + if (ret) { > + v4l_err(client, "irq%d reg failed: %d\n", client->irq, ret); > + goto err_free_media; > + } > + > + return 0; > + > +err_free_media: > + media_entity_cleanup(&sd->entity); > +err_free_mutex: > + mutex_destroy(&state->page_lock); > + mutex_destroy(&state->lock); > +err_free_state: > + kfree(state); > + dev_err(&client->dev, "%s failed: %d\n", __func__, ret); > + > + return ret; > +} > + > +static int tda1997x_remove(struct i2c_client *client) > +{ > + struct v4l2_subdev *sd = i2c_get_clientdata(client); > + struct tda1997x_state *state = to_state(sd); > + struct tda1997x_platform_data *pdata = &state->pdata; > + > + if (pdata->audout_format) { > + snd_soc_unregister_codec(&client->dev); > + mutex_destroy(&state->audio_lock); > + } > + > + disable_irq(state->client->irq); > + tda1997x_power_mode(state, 0); > + > + v4l2_async_unregister_subdev(sd); > + media_entity_cleanup(&sd->entity); > + regulator_bulk_disable(TDA1997X_NUM_SUPPLIES, state->supplies); > + i2c_unregister_device(state->client_cec); > + mutex_destroy(&state->page_lock); > + mutex_destroy(&state->lock); > + > + kfree(state); > + > + return 0; > +} > + > +static struct i2c_driver tda1997x_i2c_driver = { > + .driver = { > + .name = "tda1997x", > + .owner = THIS_MODULE, > + .of_match_table = of_match_ptr(tda1997x_of_id), > + }, > + .probe = tda1997x_probe, > + .remove = tda1997x_remove, > + .id_table = tda1997x_i2c_id, > +}; > + > +module_i2c_driver(tda1997x_i2c_driver); > + > +MODULE_AUTHOR("Tim Harvey <tharvey@gateworks.com>"); > +MODULE_DESCRIPTION("TDA1997X HDMI Receiver driver"); > +MODULE_LICENSE("GPL v2"); > diff --git a/include/dt-bindings/media/tda1997x.h b/include/dt-bindings/media/tda1997x.h > new file mode 100644 > index 0000000..f4cdf87 > --- /dev/null > +++ b/include/dt-bindings/media/tda1997x.h > @@ -0,0 +1,78 @@ > +/* > + * Copyright (C) 2017 Gateworks Corporation > + * > + * 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. > + */ > +#ifndef _DT_BINDINGS_MEDIA_TDA1997X_H > +#define _DT_BINDINGS_MEDIA_TDA1997X_H > + > +/* TDA19973 36bit Video Port control registers */ > +#define TDA1997X_VP36_35_32 0 > +#define TDA1997X_VP36_31_28 1 > +#define TDA1997X_VP36_27_24 2 > +#define TDA1997X_VP36_23_20 3 > +#define TDA1997X_VP36_19_16 4 > +#define TDA1997X_VP36_15_12 5 > +#define TDA1997X_VP36_11_08 6 > +#define TDA1997X_VP36_07_04 7 > +#define TDA1997X_VP36_03_00 8 > + > +/* TDA19971 24bit Video Port control registers */ > +#define TDA1997X_VP24_V23_20 0 > +#define TDA1997X_VP24_V19_16 1 > +#define TDA1997X_VP24_V15_12 3 > +#define TDA1997X_VP24_V11_08 4 > +#define TDA1997X_VP24_V07_04 6 > +#define TDA1997X_VP24_V03_00 7 > + > +/* Pin groups */ > +#define TDA1997X_VP_OUT_EN 0x80 /* enable output group */ > +#define TDA1997X_VP_HIZ 0x40 /* hi-Z output group when not used */ > +#define TDA1997X_VP_SWP 0x10 /* pin-swap output group */ > +#define TDA1997X_R_CR_CBCR_3_0 (0 | TDA1997X_VP_OUT_EN | TDA1997X_VP_HIZ) > +#define TDA1997X_R_CR_CBCR_7_4 (1 | TDA1997X_VP_OUT_EN | TDA1997X_VP_HIZ) > +#define TDA1997X_R_CR_CBCR_11_8 (2 | TDA1997X_VP_OUT_EN | TDA1997X_VP_HIZ) > +#define TDA1997X_B_CB_3_0 (3 | TDA1997X_VP_OUT_EN | TDA1997X_VP_HIZ) > +#define TDA1997X_B_CB_7_4 (4 | TDA1997X_VP_OUT_EN | TDA1997X_VP_HIZ) > +#define TDA1997X_B_CB_11_8 (5 | TDA1997X_VP_OUT_EN | TDA1997X_VP_HIZ) > +#define TDA1997X_G_Y_3_0 (6 | TDA1997X_VP_OUT_EN | TDA1997X_VP_HIZ) > +#define TDA1997X_G_Y_7_4 (7 | TDA1997X_VP_OUT_EN | TDA1997X_VP_HIZ) > +#define TDA1997X_G_Y_11_8 (8 | TDA1997X_VP_OUT_EN | TDA1997X_VP_HIZ) > +/* pinswapped groups */ > +#define TDA1997X_R_CR_CBCR_3_0_S (TDA1997X_R_CR_CBCR_3_0 | TDA1997X_VP_SWAP) > +#define TDA1997X_R_CR_CBCR_7_4_S (TDA1997X_R_CR_CBCR_7_4 | TDA1997X_VP_SWAP) > +#define TDA1997X_R_CR_CBCR_11_8_S (TDA1997X_R_CR_CBCR_11_8 | TDA1997X_VP_SWAP) > +#define TDA1997X_B_CB_3_0_S (TDA1997X_B_CB_3_0 | TDA1997X_VP_SWAP) > +#define TDA1997X_B_CB_7_4_S (TDA1997X_B_CB_7_4 | TDA1997X_VP_SWAP) > +#define TDA1997X_B_CB_11_8_S (TDA1997X_B_CB_11_8 | TDA1997X_VP_SWAP) > +#define TDA1997X_G_Y_3_0_S (TDA1997X_G_Y_3_0 | TDA1997X_VP_SWAP) > +#define TDA1997X_G_Y_7_4_S (TDA1997X_G_Y_7_4 | TDA1997X_VP_SWAP) > +#define TDA1997X_G_Y_11_8_S (TDA1997X_G_Y_11_8 | TDA1997X_VP_SWAP) > + > +/* Audio bus DAI format */ > +#define TDA1997X_I2S16 1 /* I2S 16bit */ > +#define TDA1997X_I2S32 2 /* I2S 32bit */ > +#define TDA1997X_SPDIF 3 /* SPDIF */ > +#define TDA1997X_OBA 4 /* One Bit Audio */ > +#define TDA1997X_DST 5 /* Direct Stream Transfer */ > +#define TDA1997X_I2S16_HBR 6 /* HBR straight in I2S 16bit mode */ > +#define TDA1997X_I2S16_HBR_DEMUX 7 /* HBR demux in I2S 16bit mode */ > +#define TDA1997X_I2S32_HBR_DEMUX 8 /* HBR demux in I2S 32bit mode */ > +#define TDA1997X_SPDIF_HBR_DEMUX 9 /* HBR demux in SPDIF mode */ > + > +/* Audio bus channel layout */ > +#define TDA1997X_LAYOUT0 0 /* 2-channel */ > +#define TDA1997X_LAYOUT1 1 /* 8-channel */ > + > +/* Audio bus clock */ > +#define TDA1997X_ACLK_16FS 0 > +#define TDA1997X_ACLK_32FS 1 > +#define TDA1997X_ACLK_64FS 2 > +#define TDA1997X_ACLK_128FS 3 > +#define TDA1997X_ACLK_256FS 4 > +#define TDA1997X_ACLK_512FS 5 > + > +#endif /* _DT_BINDINGS_MEDIA_TDA1997X_H */ > diff --git a/include/media/i2c/tda1997x.h b/include/media/i2c/tda1997x.h > new file mode 100644 > index 0000000..50cb847 > --- /dev/null > +++ b/include/media/i2c/tda1997x.h > @@ -0,0 +1,53 @@ > +/* > + * tda1997x - NXP HDMI receiver > + * > + * Copyright 2017 Tim Harvey <tharvey@gateworks.com> > + * > + * This program is free software; you may redistribute it and/or modify > + * it under the terms of the GNU General Public License as published by > + * the Free Software Foundation; version 2 of the License. > + * > + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, > + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF > + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND > + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS > + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN > + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN > + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE > + * SOFTWARE. > + * > + */ > + > +#ifndef _TDA1997X_ > +#define _TDA1997X_ > + > +/* Platform Data */ > +struct tda1997x_platform_data { > + u32 vidout_bus_width; > + u8 vidout_port_cfg[9]; > + /* pin polarity (1=invert) */ > + bool vidout_inv_de; > + bool vidout_inv_hs; > + bool vidout_inv_vs; > + bool vidout_inv_pclk; > + /* clock delays (0=-8, 1=-7 ... 15=+7 pixels) */ > + u8 vidout_delay_hs; > + u8 vidout_delay_vs; > + u8 vidout_delay_de; > + u8 vidout_delay_pclk; > + /* sync selections (controls how sync pins are derived) */ > + u8 vidout_sel_hs; > + u8 vidout_sel_vs; > + u8 vidout_sel_de; > + > + /* Audio Port Output */ > + int audout_format; > + u32 audout_mclk_fs; /* clock multiplier */ > + u32 audout_width; /* 13 or 32 bit */ > + u32 audout_layout; /* layout0=AP0 layout1=AP0,AP1,AP2,AP3 */ > + bool audout_layoutauto; /* audio layout dictated by pkt header */ > + bool audout_invert_clk; /* data valid on rising edge of BCLK */ > + bool audio_auto_mute; /* enable hardware audio auto-mute */ > +}; > + > +#endif > Regards, Hans
On 10/18/2017 02:04 PM, Hans Verkuil wrote: > Hi Tim, > > Here is my review of this v2: > > On 10/12/17 06:45, Tim Harvey wrote: >> Add support for the TDA1997x HDMI receivers. >> >> Cc: Hans Verkuil <hverkuil@xs4all.nl> >> Signed-off-by: Tim Harvey <tharvey@gateworks.com> >> --- >> v2: >> - implement dv timings enum/cap >> - remove deprecated g_mbus_config op >> - fix dv_query_timings >> - add EDID get/set handling >> - remove max-pixel-rate support >> - add audio codec DAI support >> - use new audio bindings >> >> --- >> drivers/media/i2c/Kconfig | 9 + >> drivers/media/i2c/Makefile | 1 + >> drivers/media/i2c/tda1997x.c | 3336 ++++++++++++++++++++++++++++++++++ >> include/dt-bindings/media/tda1997x.h | 78 + >> include/media/i2c/tda1997x.h | 53 + >> 5 files changed, 3477 insertions(+) >> create mode 100644 drivers/media/i2c/tda1997x.c >> create mode 100644 include/dt-bindings/media/tda1997x.h >> create mode 100644 include/media/i2c/tda1997x.h >> <snip> >> +/* parse an infoframe and do some sanity checks on it */ >> +static unsigned int >> +tda1997x_parse_infoframe(struct tda1997x_state *state, u16 addr) >> +{ >> + struct v4l2_subdev *sd = &state->sd; >> + union hdmi_infoframe frame; >> + u8 buffer[40]; >> + u8 reg; >> + int len, err; >> + >> + /* read data */ >> + len = io_readn(sd, addr, sizeof(buffer), buffer); >> + err = hdmi_infoframe_unpack(&frame, buffer); >> + if (err) { >> + v4l_err(state->client, >> + "failed parsing %d byte infoframe: 0x%04x/0x%02x\n", >> + len, addr, buffer[0]); >> + return err; >> + } >> + if (debug > 1) >> + hdmi_infoframe_log(KERN_INFO, &state->client->dev, &frame); >> + switch (frame.any.type) { >> + /* Audio InfoFrame: see HDMI spec 8.2.2 */ >> + case HDMI_INFOFRAME_TYPE_AUDIO: >> + /* sample rate */ >> + switch (frame.audio.sample_frequency) { >> + case HDMI_AUDIO_SAMPLE_FREQUENCY_32000: >> + state->audio_samplerate = 32000; >> + break; >> + case HDMI_AUDIO_SAMPLE_FREQUENCY_44100: >> + state->audio_samplerate = 44100; >> + break; >> + case HDMI_AUDIO_SAMPLE_FREQUENCY_48000: >> + state->audio_samplerate = 48000; >> + break; >> + case HDMI_AUDIO_SAMPLE_FREQUENCY_88200: >> + state->audio_samplerate = 88200; >> + break; >> + case HDMI_AUDIO_SAMPLE_FREQUENCY_96000: >> + state->audio_samplerate = 96000; >> + break; >> + case HDMI_AUDIO_SAMPLE_FREQUENCY_176400: >> + state->audio_samplerate = 176400; >> + break; >> + case HDMI_AUDIO_SAMPLE_FREQUENCY_192000: >> + state->audio_samplerate = 192000; >> + break; >> + default: >> + case HDMI_AUDIO_SAMPLE_FREQUENCY_STREAM: >> + break; >> + } >> + >> + /* sample size */ >> + switch (frame.audio.sample_size) { >> + case HDMI_AUDIO_SAMPLE_SIZE_16: >> + state->audio_samplesize = 16; >> + break; >> + case HDMI_AUDIO_SAMPLE_SIZE_20: >> + state->audio_samplesize = 20; >> + break; >> + case HDMI_AUDIO_SAMPLE_SIZE_24: >> + state->audio_samplesize = 24; >> + break; >> + case HDMI_AUDIO_SAMPLE_SIZE_STREAM: >> + default: >> + break; >> + } >> + >> + /* Channel Count */ >> + state->audio_channels = frame.audio.channels; >> + if (frame.audio.channel_allocation && >> + frame.audio.channel_allocation != state->audio_ch_alloc) { >> + /* use the channel assignment from the infoframe */ >> + state->audio_ch_alloc = frame.audio.channel_allocation; >> + tda1997x_configure_audout(sd, state->audio_ch_alloc); >> + /* reset the audio FIFO */ >> + tda1997x_hdmi_info_reset(sd, RESET_AUDIO, false); >> + } >> + break; >> + >> + /* Source Product Descriptor information (SPD) */ >> + case HDMI_INFOFRAME_TYPE_SPD: >> + strncpy(frame.spd.vendor, state->vendor, >> + sizeof(frame.spd.vendor)); >> + strncpy(frame.spd.product, state->product, >> + sizeof(frame.spd.product)); >> + v4l_info(state->client, "Source Product Descriptor: %s %s\n", >> + state->vendor, state->product); > > Use hdmi_infoframe_log() for logging infoframes. > >> + break; >> + >> + /* Auxiliary Video information (AVI) InfoFrame: see HDMI spec 8.2.1 */ >> + case HDMI_INFOFRAME_TYPE_AVI: >> + state->colorspace = frame.avi.colorspace; >> + state->colorimetry = frame.avi.colorimetry; >> + /* >> + * If colorimetry not specified, conversion depends on res type: >> + * - SDTV: ITU601 for SD (480/576/240/288 line resolution) >> + * - HDTV: ITU709 for HD (720/1080 line resolution) >> + * - PC: sRGB >> + * see HDMI specification section 6.7 >> + */ >> + if ((state->colorspace == HDMI_COLORSPACE_YUV422 || >> + state->colorspace == HDMI_COLORSPACE_YUV444) && >> + (state->colorimetry == HDMI_COLORIMETRY_EXTENDED || >> + state->colorimetry == HDMI_COLORIMETRY_NONE)) { >> + switch (state->timings.bt.height) { >> + case 480: >> + case 576: >> + case 240: >> + case 288: >> + state->colorimetry = HDMI_COLORIMETRY_ITU_601; >> + break; >> + case 720: >> + case 1080: >> + state->colorimetry = HDMI_COLORIMETRY_ITU_709; >> + break; >> + default: >> + state->colorimetry = HDMI_COLORIMETRY_NONE; > > Missing break. > >> + } >> + } >> + v4l_dbg(1, debug, state->client, >> + "Colorspace=%d Colorimetry=%d\n", >> + state->colorspace, state->colorimetry); >> + >> + /* configure upsampler: 0=bypass 1=repeatchroma 2=interpolate */ >> + reg = io_read(sd, REG_PIX_REPEAT); >> + reg &= ~PIX_REPEAT_MASK_UP_SEL; >> + if (state->colorspace == HDMI_COLORSPACE_YUV422) >> + reg |= (PIX_REPEAT_CHROMA << PIX_REPEAT_SHIFT); >> + io_write(sd, REG_PIX_REPEAT, reg); >> + >> + /* ConfigurePixelRepeater: repeat n-times each pixel */ >> + reg = io_read(sd, REG_PIX_REPEAT); >> + reg &= ~PIX_REPEAT_MASK_REP; >> + reg |= frame.avi.pixel_repeat; >> + io_write(sd, REG_PIX_REPEAT, reg); >> + >> + /* configure the receiver with the new colorspace */ >> + tda1997x_configure_conv(sd, state->colorspace, >> + state->colorimetry); > > What I am missing here is handling of the RGB quantization range. > An HDMI receiver will typically send full range RGB or limited range YUV > to the SoC. The HDMI source can however send full or limited range RGB > or limited range YUV (full range YUV is theoretically possible, but nobody > does that). > > For a Full HD receiver the rules when receiving RGB video are as follows: > > If the EDID supports selectable RGB Quantization Range, then check if the > source explicitly sets the RGB quantization range in the AVI InfoFrame and > use that value. A small correction here: while ideally you should indeed check if the current EDID supports selectable RGB Quantization Range, in practice you don't need to. If the source explicitly sets the RGB quantization range, then just use that. Note: some hardware can do this automatically (adv7604) by detecting what is transmitted in the AVI InfoFrame. That's probably not the case here since you have to provide a conversion matrix. > > Otherwise fall back to the default rules: > > if VIC == 0, then expect full range RGB, otherwise expect limited range RGB. > > It gets even more complicated with 4k video, but this is full HD only. > > In addition, you may also want to implement the V4L2_CID_DV_RX_RGB_RANGE control > to let userspace override the autodetection. To clarify: this control makes it possible to override the default rules. Too many sources (Hi Apple!) make a mess of this and send e.g. full range when they should be sending limited range or vice versa. > > RGB Quantization Range handling is *the* biggest headache for HDMI receivers. > > If you happen to attend the Embedded Linux Conference Europe in Prague next > week, then attend my presentation on HDMI 4k Video on the Wednesday for all > the reasons why this is so tricky. If you have any questions, feel free to ask. This is a nasty corner of the HDMI spec but unfortunately one that is very visible to the user if you do it wrong. Regards, Hans
On Wed, Oct 18, 2017 at 5:04 AM, Hans Verkuil <hverkuil@xs4all.nl> wrote: > Hi Tim, > > Here is my review of this v2: > > On 10/12/17 06:45, Tim Harvey wrote: >> Add support for the TDA1997x HDMI receivers. >> <snip> >> + >> +/* >> + * Video Input formats >> + */ >> +struct vhref_values { >> + u16 href_start; >> + u16 href_end; >> + u16 vref_f1_start; >> + u8 vref_f1_width; >> + u16 vref_f2_start; >> + u8 vref_f2_width; >> + u16 fieldref_f1_start; >> + u8 fieldPolarity; >> + u16 fieldref_f2_start; > > Since we don't support interlaced (yet) I'd just drop the 'f2' fields. > Ditto for fieldPolarity. > > Can't these href/vref values be calculated from the timings? > The values in this struct are used to configure the tda1997x VHREF timing generator in tda1997x_configure_input_resolution() for generating the video output bus timings so I can't really drop them unless I can calculate them. Let me look into this - should be possible. >> +}; >> + <snip> >> +/* parse an infoframe and do some sanity checks on it */ >> +static unsigned int >> +tda1997x_parse_infoframe(struct tda1997x_state *state, u16 addr) >> +{ >> + struct v4l2_subdev *sd = &state->sd; >> + union hdmi_infoframe frame; >> + u8 buffer[40]; >> + u8 reg; >> + int len, err; >> + >> + /* read data */ >> + len = io_readn(sd, addr, sizeof(buffer), buffer); >> + err = hdmi_infoframe_unpack(&frame, buffer); >> + if (err) { >> + v4l_err(state->client, >> + "failed parsing %d byte infoframe: 0x%04x/0x%02x\n", >> + len, addr, buffer[0]); >> + return err; >> + } >> + if (debug > 1) >> + hdmi_infoframe_log(KERN_INFO, &state->client->dev, &frame); >> + switch (frame.any.type) { >> + /* Audio InfoFrame: see HDMI spec 8.2.2 */ >> + case HDMI_INFOFRAME_TYPE_AUDIO: >> + /* sample rate */ >> + switch (frame.audio.sample_frequency) { >> + case HDMI_AUDIO_SAMPLE_FREQUENCY_32000: >> + state->audio_samplerate = 32000; >> + break; >> + case HDMI_AUDIO_SAMPLE_FREQUENCY_44100: >> + state->audio_samplerate = 44100; >> + break; >> + case HDMI_AUDIO_SAMPLE_FREQUENCY_48000: >> + state->audio_samplerate = 48000; >> + break; >> + case HDMI_AUDIO_SAMPLE_FREQUENCY_88200: >> + state->audio_samplerate = 88200; >> + break; >> + case HDMI_AUDIO_SAMPLE_FREQUENCY_96000: >> + state->audio_samplerate = 96000; >> + break; >> + case HDMI_AUDIO_SAMPLE_FREQUENCY_176400: >> + state->audio_samplerate = 176400; >> + break; >> + case HDMI_AUDIO_SAMPLE_FREQUENCY_192000: >> + state->audio_samplerate = 192000; >> + break; >> + default: >> + case HDMI_AUDIO_SAMPLE_FREQUENCY_STREAM: >> + break; >> + } >> + >> + /* sample size */ >> + switch (frame.audio.sample_size) { >> + case HDMI_AUDIO_SAMPLE_SIZE_16: >> + state->audio_samplesize = 16; >> + break; >> + case HDMI_AUDIO_SAMPLE_SIZE_20: >> + state->audio_samplesize = 20; >> + break; >> + case HDMI_AUDIO_SAMPLE_SIZE_24: >> + state->audio_samplesize = 24; >> + break; >> + case HDMI_AUDIO_SAMPLE_SIZE_STREAM: >> + default: >> + break; >> + } >> + >> + /* Channel Count */ >> + state->audio_channels = frame.audio.channels; >> + if (frame.audio.channel_allocation && >> + frame.audio.channel_allocation != state->audio_ch_alloc) { >> + /* use the channel assignment from the infoframe */ >> + state->audio_ch_alloc = frame.audio.channel_allocation; >> + tda1997x_configure_audout(sd, state->audio_ch_alloc); >> + /* reset the audio FIFO */ >> + tda1997x_hdmi_info_reset(sd, RESET_AUDIO, false); >> + } >> + break; >> + >> + /* Source Product Descriptor information (SPD) */ >> + case HDMI_INFOFRAME_TYPE_SPD: >> + strncpy(frame.spd.vendor, state->vendor, >> + sizeof(frame.spd.vendor)); >> + strncpy(frame.spd.product, state->product, >> + sizeof(frame.spd.product)); >> + v4l_info(state->client, "Source Product Descriptor: %s %s\n", >> + state->vendor, state->product); > > Use hdmi_infoframe_log() for logging infoframes. ok - I will always call hdmi_infoframe_log() above and refrain from outputs that just repeat those details. > >> + break; >> + >> + /* Auxiliary Video information (AVI) InfoFrame: see HDMI spec 8.2.1 */ >> + case HDMI_INFOFRAME_TYPE_AVI: >> + state->colorspace = frame.avi.colorspace; >> + state->colorimetry = frame.avi.colorimetry; >> + /* >> + * If colorimetry not specified, conversion depends on res type: >> + * - SDTV: ITU601 for SD (480/576/240/288 line resolution) >> + * - HDTV: ITU709 for HD (720/1080 line resolution) >> + * - PC: sRGB >> + * see HDMI specification section 6.7 >> + */ >> + if ((state->colorspace == HDMI_COLORSPACE_YUV422 || >> + state->colorspace == HDMI_COLORSPACE_YUV444) && >> + (state->colorimetry == HDMI_COLORIMETRY_EXTENDED || >> + state->colorimetry == HDMI_COLORIMETRY_NONE)) { >> + switch (state->timings.bt.height) { >> + case 480: >> + case 576: >> + case 240: >> + case 288: >> + state->colorimetry = HDMI_COLORIMETRY_ITU_601; >> + break; >> + case 720: >> + case 1080: >> + state->colorimetry = HDMI_COLORIMETRY_ITU_709; >> + break; >> + default: >> + state->colorimetry = HDMI_COLORIMETRY_NONE; > > Missing break. > oops - thanks >> + } >> + } >> + v4l_dbg(1, debug, state->client, >> + "Colorspace=%d Colorimetry=%d\n", >> + state->colorspace, state->colorimetry); >> + >> + /* configure upsampler: 0=bypass 1=repeatchroma 2=interpolate */ >> + reg = io_read(sd, REG_PIX_REPEAT); >> + reg &= ~PIX_REPEAT_MASK_UP_SEL; >> + if (state->colorspace == HDMI_COLORSPACE_YUV422) >> + reg |= (PIX_REPEAT_CHROMA << PIX_REPEAT_SHIFT); >> + io_write(sd, REG_PIX_REPEAT, reg); >> + >> + /* ConfigurePixelRepeater: repeat n-times each pixel */ >> + reg = io_read(sd, REG_PIX_REPEAT); >> + reg &= ~PIX_REPEAT_MASK_REP; >> + reg |= frame.avi.pixel_repeat; >> + io_write(sd, REG_PIX_REPEAT, reg); >> + >> + /* configure the receiver with the new colorspace */ >> + tda1997x_configure_conv(sd, state->colorspace, >> + state->colorimetry); > > What I am missing here is handling of the RGB quantization range. > An HDMI receiver will typically send full range RGB or limited range YUV > to the SoC. The HDMI source can however send full or limited range RGB > or limited range YUV (full range YUV is theoretically possible, but nobody > does that). > isn't this quantization range a function of the colorspace and colorimetry dictated by the AVI infoframe? I'm taking these into consideration when setting up the conversion matrix in tda1997x_configure_conv(). > For a Full HD receiver the rules when receiving RGB video are as follows: > > If the EDID supports selectable RGB Quantization Range, then check if the > source explicitly sets the RGB quantization range in the AVI InfoFrame and > use that value. > > Otherwise fall back to the default rules: > > if VIC == 0, then expect full range RGB, otherwise expect limited range RGB. Are you referring to the video_code field of the AVI infoframe or vic from a vendor infoframe? > > It gets even more complicated with 4k video, but this is full HD only. > > In addition, you may also want to implement the V4L2_CID_DV_RX_RGB_RANGE control > to let userspace override the autodetection. I'll add that as an additional patch. Are there other V4L2_CID's that I should be adding here? > > RGB Quantization Range handling is *the* biggest headache for HDMI receivers. > > If you happen to attend the Embedded Linux Conference Europe in Prague next > week, then attend my presentation on HDMI 4k Video on the Wednesday for all > the reasons why this is so tricky. > >> + break; >> + default: >> + break; >> + } >> + return 0; >> +} >> + <snip> >> +static int tda1997x_query_dv_timings(struct v4l2_subdev *sd, >> + struct v4l2_dv_timings *timings) >> +{ >> + struct tda1997x_state *state = to_state(sd); >> + int ret; >> + >> + v4l_dbg(1, debug, state->client, "%s\n", __func__); >> + if (!timings) >> + return -EINVAL; >> + >> + memset(timings, 0, sizeof(struct v4l2_dv_timings)); >> + mutex_lock(&state->lock); >> + ret = tda1997x_detect_std(state); >> + if (ret) >> + goto error; >> + *timings = state->std->timings; >> + mutex_unlock(&state->lock); >> + return 0; >> + >> +error: >> + mutex_unlock(&state->lock); >> + return ret; > > This can be simplified: > > ret = tda1997x_detect_std(state); > if (!ret) > *timings = state->std->timings; > mutex_unlock(&state->lock); > return ret; > yes, will do >> +} >> + <snip> >> + >> +static int tda1997x_set_edid(struct v4l2_subdev *sd, struct v4l2_edid *edid) >> +{ >> + struct tda1997x_state *state = to_state(sd); >> + int i; >> + >> + v4l_dbg(1, debug, state->client, "%s pad=%d\n", __func__, edid->pad); >> + memset(edid->reserved, 0, sizeof(edid->reserved)); >> + >> + if (edid->start_block != 0) >> + return -EINVAL; >> + >> + if (edid->blocks == 0) { >> + state->edid.blocks = 0; >> + state->edid.present = 0; >> + tda1997x_manual_hpd(&state->sd, HPD_LOW_BP); >> + return 0; >> + } >> + >> + if (edid->blocks > 2) { >> + edid->blocks = 2; >> + return -E2BIG; >> + } >> + >> + /* write base EDID */ >> + for (i = 0; i < 128; i++) >> + io_write(sd, REG_EDID_IN_BYTE0 + i, edid->edid[i]); >> + >> + /* write CEA Extension */ >> + for (i = 0; i < 128; i++) >> + io_write(sd, REG_EDID_IN_BYTE128 + i, edid->edid[i+128]); >> + > > Before updating the EDID pull the HPD low. Afterwards pull it up again. > The minimum time the HPD should remain low is 100 ms. > ok - I will add a delayed work procedure to handle this. By the way, how do I get/set EDID on a v4l2-subdev? root@ventana:~# v4l2-ctl -d4 --set-edid=pad=0,type=hdmi CEA-861 Header IT Formats Underscanned: yes Audio: yes YCbCr 4:4:4: yes YCbCr 4:2:2: yes Speaker Allocation Data Block FL/FR: yes LFE: no FC: no RL/RR: no RC: no FLC/FRC: no RLC/RRC: no FLW/FRW: no FLH/FRH: no TC: no FCH: no HDMI Vendor-Specific Data Block Max TMDS Clock: 170 MHz Physical Address: 1.0.0.0 YCbCr 4:4:4 Deep Color: no 30-bit: no 36-bit: no 48-bit: no Graphics: yes Photo: no Cinema: no Game: no CEA-861 Video Capability Descriptor RGB Quantization Range: yes YCC Quantization Range: yes PT: Always Underscanned IT: Always Underscanned CE: Always Underscanned CEA-861 Colorimetry Data Block xvYCC 601: no xvYCC 709: no sYCC: no AdobeRGB: no AdobeYCC: no BT.2020 RGB: no BT.2020 YCC: no BT.2020 cYCC: no CEA-861 HDR Static Metadata Data Block SDR (Traditional Gamma): yes HDR (Traditional Gamma): no SMPTE 2084: no VIDIOC_S_EDID: failed: Inappropriate ioctl for device root@ventana:~# I'm also not clear how to run v4l2-compliance on a v4l2-subdev, so I just ran it on one of the video devs from the capture driver I'm linked to via media-ctl. >> + return 0; >> +} >> + >> +static int tda1997x_get_dv_timings_cap(struct v4l2_subdev *sd, >> + struct v4l2_dv_timings_cap *cap) >> +{ >> + *cap = tda1997x_dv_timings_cap; >> + return 0; >> +} >> + >> +static int tda1997x_enum_dv_timings(struct v4l2_subdev *sd, >> + struct v4l2_enum_dv_timings *timings) >> +{ >> + struct tda1997x_state *state = to_state(sd); >> + >> + return v4l2_enum_dv_timings_cap(timings, &tda1997x_dv_timings_cap, >> + tda1997x_check_dv_timings, state); >> +} >> + >> +static const struct v4l2_subdev_pad_ops tda1997x_pad_ops = { >> + .enum_mbus_code = tda1997x_enum_mbus_code, >> + .get_fmt = tda1997x_get_pad_format, >> + .set_fmt = tda1997x_set_pad_format, >> + .get_edid = tda1997x_get_edid, >> + .set_edid = tda1997x_set_edid, >> + .dv_timings_cap = tda1997x_get_dv_timings_cap, >> + .enum_dv_timings = tda1997x_enum_dv_timings, >> +}; >> + >> +/* ----------------------------------------------------------------------------- >> + * v4l2_subdev_core_ops >> + */ >> + >> +static int tda1997x_log_status(struct v4l2_subdev *sd) >> +{ >> + struct tda1997x_state *state = to_state(sd); >> + const struct v4l2_dv_timings *timings = &state->timings; >> + >> + v4l2_info(sd, "-----Signal status-----\n"); >> + if (!timings) { > > timings can never be NULL. oops - yes, this should be if (!state->std) to detect signal status > >> + v4l2_info(sd, "no signal\n"); >> + return 0; >> + } >> + v4l2_info(sd, "resolution: %dx%d%c@%dHz\n", >> + timings->bt.width, timings->bt.height, >> + timings->bt.interlaced ? 'i' : 'p', >> + state->fps); >> + v4l2_print_dv_timings(sd->name, "Detected format: ", >> + timings, true); >> + v4l2_info(sd, "colorspace: %d\n", state->colorspace); >> + v4l2_info(sd, "colorimetry: %d\n", state->colorimetry); >> + if (state->audio_channels) >> + v4l2_info(sd, "audio: %dch %dHz\n", state->audio_channels, >> + state->audio_samplerate); >> + else >> + v4l2_info(sd, "audio: none\n"); >> + v4l2_info(sd, "vendor: %s\n", state->vendor); >> + v4l2_info(sd, "product: %s\n", state->product); > > If at all possible you should log the received InfoFrames here (hdmi_infoframe_log). > Also whether an EDID is loaded or not and the HPD state. > > If the hardware supports 5V detection, then you should log that too. In that case > also implement support for the V4L2_CID_DV_RX_POWER_PRESENT control. > > Also any information on the signal detection (clock lock, sync lock(s), whatever). > > This all helps enormously when debugging problems. It's important to spend some > time on this function. The adv7604.c source might be a good place to look for > inspiration. ok - I will add what I can. I can't figure out how to use log-status on a subdev either: root@ventana:~# cat /sys/class/video4linux/v4l-subdev1/name tda19971 2-0048 root@ventana:~# v4l2-ctl -d /dev/v4l-subdev1 --log-status VIDIOC_QUERYCAP: failed: Inappropriate ioctl for device /dev/v4l-subdev1: not a v4l2 node > >> + >> + return 0; >> +} >> + <snip> >> + >> +static int tda1997x_core_init(struct v4l2_subdev *sd) >> +{ >> + struct tda1997x_state *state = to_state(sd); >> + struct tda1997x_platform_data *pdata = &state->pdata; >> + u8 reg; >> + int i; >> + >> + /* disable HPD */ >> + io_write(sd, REG_HPD_AUTO_CTRL, HPD_AUTO_HPD_UNSEL); >> + if (state->chip_revision == 0) { >> + io_write(sd, REG_MAN_SUS_HDMI_SEL, MAN_DIS_HDCP | MAN_RST_HDCP); >> + io_write(sd, REG_CGU_DBG_SEL, 1 << CGU_DBG_CLK_SEL_SHIFT); >> + } >> + >> + /* reset infoframe at end of start-up-sequencer */ >> + io_write(sd, REG_SUS_SET_RGB2, 0x06); >> + io_write(sd, REG_SUS_SET_RGB3, 0x06); >> + >> + /* Enable TMDS pull-ups */ >> + io_write(sd, REG_RT_MAN_CTRL, RT_MAN_CTRL_RT | >> + RT_MAN_CTRL_RT_B | RT_MAN_CTRL_RT_A); >> + >> + /* enable sync measurement timing */ >> + tda1997x_cec_write(sd, REG_PWR_CONTROL & 0xff, 0x04); >> + /* adjust CEC clock divider */ >> + tda1997x_cec_write(sd, REG_OSC_DIVIDER & 0xff, 0x03); >> + tda1997x_cec_write(sd, REG_EN_OSC_PERIOD_LSB & 0xff, 0xa0); >> + io_write(sd, REG_TIMER_D, 0x54); >> + /* enable power switch */ >> + reg = tda1997x_cec_read(sd, REG_CONTROL & 0xff); >> + reg |= 0x20; >> + tda1997x_cec_write(sd, REG_CONTROL & 0xff, reg); >> + mdelay(50); >> + >> + /* read the chip version */ >> + reg = io_read(sd, REG_VERSION); >> + /* get the chip configuration */ >> + reg = io_read(sd, REG_CMTP_REG10); >> + >> + /* enable interrupts we care about */ >> + io_write(sd, REG_INT_MASK_TOP, >> + INTERRUPT_HDCP | INTERRUPT_AUDIO | INTERRUPT_INFO | >> + INTERRUPT_RATE | INTERRUPT_SUS); >> + /* config_mtp,fmt,sus_end,sus_st */ >> + io_write(sd, REG_INT_MASK_SUS, MASK_MPT | MASK_FMT | MASK_SUS_END); >> + /* rate stability change for inputs A/B */ >> + io_write(sd, REG_INT_MASK_RATE, MASK_RATE_B_ST | MASK_RATE_A_ST); >> + /* aud,spd,avi*/ >> + io_write(sd, REG_INT_MASK_INFO, >> + MASK_AUD_IF | MASK_SPD_IF | MASK_AVI_IF); >> + /* audio_freq,audio_flg,mute_flg,fifo_err */ >> + io_write(sd, REG_INT_MASK_AUDIO, >> + MASK_AUDIO_FREQ_FLG | MASK_AUDIO_FLG | MASK_MUTE_FLG | >> + MASK_ERROR_FIFO_PT); >> + /* HDCP C5 state reached */ >> + io_write(sd, REG_INT_MASK_HDCP, MASK_STATE_C5); >> + /* don't care about AFE/DDC/MODE */ >> + io_write(sd, REG_INT_MASK_AFE, 0); >> + io_write(sd, REG_INT_MASK_DDC, 0); >> + io_write(sd, REG_INT_MASK_MODE, 0); >> + >> + /* clear all interrupts */ >> + io_write(sd, REG_INT_FLG_CLR_TOP, 0xff); >> + io_write(sd, REG_INT_FLG_CLR_SUS, 0xff); >> + io_write(sd, REG_INT_FLG_CLR_DDC, 0xff); >> + io_write(sd, REG_INT_FLG_CLR_RATE, 0xff); >> + io_write(sd, REG_INT_FLG_CLR_MODE, 0xff); >> + io_write(sd, REG_INT_FLG_CLR_INFO, 0xff); >> + io_write(sd, REG_INT_FLG_CLR_AUDIO, 0xff); >> + io_write(sd, REG_INT_FLG_CLR_HDCP, 0xff); >> + io_write(sd, REG_INT_FLG_CLR_AFE, 0xff); >> + >> + /* init TMDS equalizer */ >> + if (state->chip_revision == 0) >> + io_write(sd, REG_CGU_DBG_SEL, 1 << CGU_DBG_CLK_SEL_SHIFT); >> + io_write24(sd, REG_CLK_MIN_RATE, CLK_MIN_RATE); >> + io_write24(sd, REG_CLK_MAX_RATE, CLK_MAX_RATE); >> + if (state->chip_revision == 0) >> + io_write(sd, REG_WDL_CFG, WDL_CFG_VAL); >> + /* DC filter */ >> + io_write(sd, REG_DEEP_COLOR_CTRL, DC_FILTER_VAL); >> + /* disable test pattern */ >> + io_write(sd, REG_SVC_MODE, 0x00); >> + /* update HDMI INFO CTRL */ >> + io_write(sd, REG_INFO_CTRL, 0xff); >> + /* write HDMI INFO EXCEED value */ >> + io_write(sd, REG_INFO_EXCEED, 3); >> + >> + if (state->chip_revision == 0) >> + tda1997x_reset_n1(state); >> + >> + /* >> + * No HDCP acknowledge when HDCP is disabled >> + * and reset SUS to force format detection >> + */ >> + tda1997x_hdmi_info_reset(sd, NACK_HDCP, true); >> + >> + /* Set HPD low */ >> + tda1997x_manual_hpd(sd, HPD_LOW_BP); >> + >> + /* Configure receiver capabilities */ >> + io_write(sd, REG_HDCP_BCAPS, HDCP_HDMI | HDCP_FAST_REAUTH); >> + >> + /* Configure HDMI: Auto HDCP mode, packet controlled mute */ >> + reg = HDMI_CTRL_MUTE_AUTO << HDMI_CTRL_MUTE_SHIFT; >> + reg |= HDMI_CTRL_HDCP_AUTO << HDMI_CTRL_HDCP_SHIFT; >> + io_write(sd, REG_HDMI_CTRL, reg); >> + >> + /* reset start-up-sequencer to force format detection */ >> + tda1997x_hdmi_info_reset(sd, 0, true); >> + >> + /* Set HPD high */ >> + tda1997x_manual_hpd(sd, HPD_HIGH_OTHER); >> + tda1997x_manual_hpd(sd, HPD_HIGH_BP); > > How can you set the HPD high if there is no EDID? No EDID, no HPD. > right - I'll remove this >> + >> + /* disable matrix conversion */ >> + reg = io_read(sd, REG_VDP_CTRL); >> + reg |= VDP_CTRL_MATRIX_BP; >> + io_write(sd, REG_VDP_CTRL, reg); >> + <snip> >> + >> + ret = 0x34 + ((io_read(sd, REG_SLAVE_ADDR)>>4) & 0x03); >> + state->client_cec = i2c_new_dummy(client->adapter, ret); >> + v4l_info(client, "CEC slave address 0x%02x\n", ret); >> + >> + ret = tda1997x_core_init(sd); > > Unless I missed it, I don't think state->timings has been initialized > to something valid. During probe the hdmi receiver has to be initialized > to something. The API expects that. Usually VGA or 720p60 or 1080p60 is > chosen for this. you didn't miss it - I didn't know exactly what to do there. I'll initialize it to VGA > >> + if (ret) >> + goto err_free_mutex; >> + <snip> >> > > Regards, > > Hans Regarding video standard detection where this chip provides me with vertical-period, horizontal-period, and horizontal-pulse-width I should be able to detect the standard simply based off of vertical-period (framerate) and horizontal-period (line width including blanking) right? I wasn't sure if my method of matching these within 14% tolerance made sense. I will be removing the hsmatch logic from that as it seems the horizontal-pulse-width should be irrelevant. Thanks for the review! Tim
On 10/19/2017 09:20 AM, Tim Harvey wrote: > On Wed, Oct 18, 2017 at 5:04 AM, Hans Verkuil <hverkuil@xs4all.nl> wrote: >> Hi Tim, >> >> Here is my review of this v2: >> >> On 10/12/17 06:45, Tim Harvey wrote: >>> Add support for the TDA1997x HDMI receivers. >>> > <snip> >>> + >>> +/* >>> + * Video Input formats >>> + */ >>> +struct vhref_values { >>> + u16 href_start; >>> + u16 href_end; >>> + u16 vref_f1_start; >>> + u8 vref_f1_width; >>> + u16 vref_f2_start; >>> + u8 vref_f2_width; >>> + u16 fieldref_f1_start; >>> + u8 fieldPolarity; >>> + u16 fieldref_f2_start; >> >> Since we don't support interlaced (yet) I'd just drop the 'f2' fields. >> Ditto for fieldPolarity. >> >> Can't these href/vref values be calculated from the timings? >> > > The values in this struct are used to configure the tda1997x VHREF > timing generator in tda1997x_configure_input_resolution() for > generating the video output bus timings so I can't really drop them > unless I can calculate them. Let me look into this - should be > possible. > >>> +}; >>> + > <snip> >>> +/* parse an infoframe and do some sanity checks on it */ >>> +static unsigned int >>> +tda1997x_parse_infoframe(struct tda1997x_state *state, u16 addr) >>> +{ >>> + struct v4l2_subdev *sd = &state->sd; >>> + union hdmi_infoframe frame; >>> + u8 buffer[40]; >>> + u8 reg; >>> + int len, err; >>> + >>> + /* read data */ >>> + len = io_readn(sd, addr, sizeof(buffer), buffer); >>> + err = hdmi_infoframe_unpack(&frame, buffer); >>> + if (err) { >>> + v4l_err(state->client, >>> + "failed parsing %d byte infoframe: 0x%04x/0x%02x\n", >>> + len, addr, buffer[0]); >>> + return err; >>> + } >>> + if (debug > 1) >>> + hdmi_infoframe_log(KERN_INFO, &state->client->dev, &frame); >>> + switch (frame.any.type) { >>> + /* Audio InfoFrame: see HDMI spec 8.2.2 */ >>> + case HDMI_INFOFRAME_TYPE_AUDIO: >>> + /* sample rate */ >>> + switch (frame.audio.sample_frequency) { >>> + case HDMI_AUDIO_SAMPLE_FREQUENCY_32000: >>> + state->audio_samplerate = 32000; >>> + break; >>> + case HDMI_AUDIO_SAMPLE_FREQUENCY_44100: >>> + state->audio_samplerate = 44100; >>> + break; >>> + case HDMI_AUDIO_SAMPLE_FREQUENCY_48000: >>> + state->audio_samplerate = 48000; >>> + break; >>> + case HDMI_AUDIO_SAMPLE_FREQUENCY_88200: >>> + state->audio_samplerate = 88200; >>> + break; >>> + case HDMI_AUDIO_SAMPLE_FREQUENCY_96000: >>> + state->audio_samplerate = 96000; >>> + break; >>> + case HDMI_AUDIO_SAMPLE_FREQUENCY_176400: >>> + state->audio_samplerate = 176400; >>> + break; >>> + case HDMI_AUDIO_SAMPLE_FREQUENCY_192000: >>> + state->audio_samplerate = 192000; >>> + break; >>> + default: >>> + case HDMI_AUDIO_SAMPLE_FREQUENCY_STREAM: >>> + break; >>> + } >>> + >>> + /* sample size */ >>> + switch (frame.audio.sample_size) { >>> + case HDMI_AUDIO_SAMPLE_SIZE_16: >>> + state->audio_samplesize = 16; >>> + break; >>> + case HDMI_AUDIO_SAMPLE_SIZE_20: >>> + state->audio_samplesize = 20; >>> + break; >>> + case HDMI_AUDIO_SAMPLE_SIZE_24: >>> + state->audio_samplesize = 24; >>> + break; >>> + case HDMI_AUDIO_SAMPLE_SIZE_STREAM: >>> + default: >>> + break; >>> + } >>> + >>> + /* Channel Count */ >>> + state->audio_channels = frame.audio.channels; >>> + if (frame.audio.channel_allocation && >>> + frame.audio.channel_allocation != state->audio_ch_alloc) { >>> + /* use the channel assignment from the infoframe */ >>> + state->audio_ch_alloc = frame.audio.channel_allocation; >>> + tda1997x_configure_audout(sd, state->audio_ch_alloc); >>> + /* reset the audio FIFO */ >>> + tda1997x_hdmi_info_reset(sd, RESET_AUDIO, false); >>> + } >>> + break; >>> + >>> + /* Source Product Descriptor information (SPD) */ >>> + case HDMI_INFOFRAME_TYPE_SPD: >>> + strncpy(frame.spd.vendor, state->vendor, >>> + sizeof(frame.spd.vendor)); >>> + strncpy(frame.spd.product, state->product, >>> + sizeof(frame.spd.product)); >>> + v4l_info(state->client, "Source Product Descriptor: %s %s\n", >>> + state->vendor, state->product); >> >> Use hdmi_infoframe_log() for logging infoframes. > > ok - I will always call hdmi_infoframe_log() above and refrain from > outputs that just repeat those details. > >> >>> + break; >>> + >>> + /* Auxiliary Video information (AVI) InfoFrame: see HDMI spec 8.2.1 */ >>> + case HDMI_INFOFRAME_TYPE_AVI: >>> + state->colorspace = frame.avi.colorspace; >>> + state->colorimetry = frame.avi.colorimetry; >>> + /* >>> + * If colorimetry not specified, conversion depends on res type: >>> + * - SDTV: ITU601 for SD (480/576/240/288 line resolution) >>> + * - HDTV: ITU709 for HD (720/1080 line resolution) >>> + * - PC: sRGB >>> + * see HDMI specification section 6.7 >>> + */ >>> + if ((state->colorspace == HDMI_COLORSPACE_YUV422 || >>> + state->colorspace == HDMI_COLORSPACE_YUV444) && >>> + (state->colorimetry == HDMI_COLORIMETRY_EXTENDED || >>> + state->colorimetry == HDMI_COLORIMETRY_NONE)) { >>> + switch (state->timings.bt.height) { >>> + case 480: >>> + case 576: >>> + case 240: >>> + case 288: >>> + state->colorimetry = HDMI_COLORIMETRY_ITU_601; >>> + break; >>> + case 720: >>> + case 1080: >>> + state->colorimetry = HDMI_COLORIMETRY_ITU_709; >>> + break; >>> + default: >>> + state->colorimetry = HDMI_COLORIMETRY_NONE; >> >> Missing break. >> > > oops - thanks > >>> + } >>> + } >>> + v4l_dbg(1, debug, state->client, >>> + "Colorspace=%d Colorimetry=%d\n", >>> + state->colorspace, state->colorimetry); >>> + >>> + /* configure upsampler: 0=bypass 1=repeatchroma 2=interpolate */ >>> + reg = io_read(sd, REG_PIX_REPEAT); >>> + reg &= ~PIX_REPEAT_MASK_UP_SEL; >>> + if (state->colorspace == HDMI_COLORSPACE_YUV422) >>> + reg |= (PIX_REPEAT_CHROMA << PIX_REPEAT_SHIFT); >>> + io_write(sd, REG_PIX_REPEAT, reg); >>> + >>> + /* ConfigurePixelRepeater: repeat n-times each pixel */ >>> + reg = io_read(sd, REG_PIX_REPEAT); >>> + reg &= ~PIX_REPEAT_MASK_REP; >>> + reg |= frame.avi.pixel_repeat; >>> + io_write(sd, REG_PIX_REPEAT, reg); >>> + >>> + /* configure the receiver with the new colorspace */ >>> + tda1997x_configure_conv(sd, state->colorspace, >>> + state->colorimetry); >> >> What I am missing here is handling of the RGB quantization range. >> An HDMI receiver will typically send full range RGB or limited range YUV >> to the SoC. The HDMI source can however send full or limited range RGB >> or limited range YUV (full range YUV is theoretically possible, but nobody >> does that). >> > > isn't this quantization range a function of the colorspace and > colorimetry dictated by the AVI infoframe? I'm taking these into > consideration when setting up the conversion matrix in > tda1997x_configure_conv(). No, it's independent of that. > >> For a Full HD receiver the rules when receiving RGB video are as follows: >> >> If the EDID supports selectable RGB Quantization Range, then check if the >> source explicitly sets the RGB quantization range in the AVI InfoFrame and >> use that value. >> >> Otherwise fall back to the default rules: >> >> if VIC == 0, then expect full range RGB, otherwise expect limited range RGB. > > Are you referring to the video_code field of the AVI infoframe or vic > from a vendor infoframe? AVI InfoFrame. The HDMI VIC codes in the vendor InfoFrame are only valid for 4k formats. And that's not supported by this device, right? > >> >> It gets even more complicated with 4k video, but this is full HD only. >> >> In addition, you may also want to implement the V4L2_CID_DV_RX_RGB_RANGE control >> to let userspace override the autodetection. > > I'll add that as an additional patch. Are there other V4L2_CID's that > I should be adding here? V4L2_CID_DV_RX_POWER_PRESENT (if possible) and optionally V4L2_CID_DV_RX_IT_CONTENT_TYPE. > >> >> RGB Quantization Range handling is *the* biggest headache for HDMI receivers. >> >> If you happen to attend the Embedded Linux Conference Europe in Prague next >> week, then attend my presentation on HDMI 4k Video on the Wednesday for all >> the reasons why this is so tricky. >> >>> + break; >>> + default: >>> + break; >>> + } >>> + return 0; >>> +} >>> + > <snip> >>> +static int tda1997x_query_dv_timings(struct v4l2_subdev *sd, >>> + struct v4l2_dv_timings *timings) >>> +{ >>> + struct tda1997x_state *state = to_state(sd); >>> + int ret; >>> + >>> + v4l_dbg(1, debug, state->client, "%s\n", __func__); >>> + if (!timings) >>> + return -EINVAL; >>> + >>> + memset(timings, 0, sizeof(struct v4l2_dv_timings)); >>> + mutex_lock(&state->lock); >>> + ret = tda1997x_detect_std(state); >>> + if (ret) >>> + goto error; >>> + *timings = state->std->timings; >>> + mutex_unlock(&state->lock); >>> + return 0; >>> + >>> +error: >>> + mutex_unlock(&state->lock); >>> + return ret; >> >> This can be simplified: >> >> ret = tda1997x_detect_std(state); >> if (!ret) >> *timings = state->std->timings; >> mutex_unlock(&state->lock); >> return ret; >> > > yes, will do > >>> +} >>> + > <snip> >>> + >>> +static int tda1997x_set_edid(struct v4l2_subdev *sd, struct v4l2_edid *edid) >>> +{ >>> + struct tda1997x_state *state = to_state(sd); >>> + int i; >>> + >>> + v4l_dbg(1, debug, state->client, "%s pad=%d\n", __func__, edid->pad); >>> + memset(edid->reserved, 0, sizeof(edid->reserved)); >>> + >>> + if (edid->start_block != 0) >>> + return -EINVAL; >>> + >>> + if (edid->blocks == 0) { >>> + state->edid.blocks = 0; >>> + state->edid.present = 0; >>> + tda1997x_manual_hpd(&state->sd, HPD_LOW_BP); >>> + return 0; >>> + } >>> + >>> + if (edid->blocks > 2) { >>> + edid->blocks = 2; >>> + return -E2BIG; >>> + } >>> + >>> + /* write base EDID */ >>> + for (i = 0; i < 128; i++) >>> + io_write(sd, REG_EDID_IN_BYTE0 + i, edid->edid[i]); >>> + >>> + /* write CEA Extension */ >>> + for (i = 0; i < 128; i++) >>> + io_write(sd, REG_EDID_IN_BYTE128 + i, edid->edid[i+128]); >>> + >> >> Before updating the EDID pull the HPD low. Afterwards pull it up again. >> The minimum time the HPD should remain low is 100 ms. >> > > ok - I will add a delayed work procedure to handle this. > > By the way, how do I get/set EDID on a v4l2-subdev? I have a patch for that adding support for this to v4l2-ctl, but it has not been posted yet. I'll see if I can get it out today/tomorrow on the mailinglist. > > root@ventana:~# v4l2-ctl -d4 --set-edid=pad=0,type=hdmi > > CEA-861 Header > IT Formats Underscanned: yes > Audio: yes > YCbCr 4:4:4: yes > YCbCr 4:2:2: yes > > Speaker Allocation Data Block > FL/FR: yes > LFE: no > FC: no > RL/RR: no > RC: no > FLC/FRC: no > RLC/RRC: no > FLW/FRW: no > FLH/FRH: no > TC: no > FCH: no > > HDMI Vendor-Specific Data Block > Max TMDS Clock: 170 MHz > Physical Address: 1.0.0.0 > YCbCr 4:4:4 Deep Color: no > 30-bit: no > 36-bit: no > 48-bit: no > Graphics: yes > Photo: no > Cinema: no > Game: no > > CEA-861 Video Capability Descriptor > RGB Quantization Range: yes > YCC Quantization Range: yes > PT: Always Underscanned > IT: Always Underscanned > CE: Always Underscanned > > CEA-861 Colorimetry Data Block > xvYCC 601: no > xvYCC 709: no > sYCC: no > AdobeRGB: no > AdobeYCC: no > BT.2020 RGB: no > BT.2020 YCC: no > BT.2020 cYCC: no > > CEA-861 HDR Static Metadata Data Block > SDR (Traditional Gamma): yes > HDR (Traditional Gamma): no > SMPTE 2084: no > VIDIOC_S_EDID: failed: Inappropriate ioctl for device > root@ventana:~# > > I'm also not clear how to run v4l2-compliance on a v4l2-subdev, so I > just ran it on one of the video devs from the capture driver I'm > linked to via media-ctl. Same problem with v4l2-ctl: no v4l-subdev support in v4l2-compliance yet. Some work is done there as well. > >>> + return 0; >>> +} >>> + >>> +static int tda1997x_get_dv_timings_cap(struct v4l2_subdev *sd, >>> + struct v4l2_dv_timings_cap *cap) >>> +{ >>> + *cap = tda1997x_dv_timings_cap; >>> + return 0; >>> +} >>> + >>> +static int tda1997x_enum_dv_timings(struct v4l2_subdev *sd, >>> + struct v4l2_enum_dv_timings *timings) >>> +{ >>> + struct tda1997x_state *state = to_state(sd); >>> + >>> + return v4l2_enum_dv_timings_cap(timings, &tda1997x_dv_timings_cap, >>> + tda1997x_check_dv_timings, state); >>> +} >>> + >>> +static const struct v4l2_subdev_pad_ops tda1997x_pad_ops = { >>> + .enum_mbus_code = tda1997x_enum_mbus_code, >>> + .get_fmt = tda1997x_get_pad_format, >>> + .set_fmt = tda1997x_set_pad_format, >>> + .get_edid = tda1997x_get_edid, >>> + .set_edid = tda1997x_set_edid, >>> + .dv_timings_cap = tda1997x_get_dv_timings_cap, >>> + .enum_dv_timings = tda1997x_enum_dv_timings, >>> +}; >>> + >>> +/* ----------------------------------------------------------------------------- >>> + * v4l2_subdev_core_ops >>> + */ >>> + >>> +static int tda1997x_log_status(struct v4l2_subdev *sd) >>> +{ >>> + struct tda1997x_state *state = to_state(sd); >>> + const struct v4l2_dv_timings *timings = &state->timings; >>> + >>> + v4l2_info(sd, "-----Signal status-----\n"); >>> + if (!timings) { >> >> timings can never be NULL. > > oops - yes, this should be if (!state->std) to detect signal status > >> >>> + v4l2_info(sd, "no signal\n"); >>> + return 0; >>> + } >>> + v4l2_info(sd, "resolution: %dx%d%c@%dHz\n", >>> + timings->bt.width, timings->bt.height, >>> + timings->bt.interlaced ? 'i' : 'p', >>> + state->fps); >>> + v4l2_print_dv_timings(sd->name, "Detected format: ", >>> + timings, true); >>> + v4l2_info(sd, "colorspace: %d\n", state->colorspace); >>> + v4l2_info(sd, "colorimetry: %d\n", state->colorimetry); >>> + if (state->audio_channels) >>> + v4l2_info(sd, "audio: %dch %dHz\n", state->audio_channels, >>> + state->audio_samplerate); >>> + else >>> + v4l2_info(sd, "audio: none\n"); >>> + v4l2_info(sd, "vendor: %s\n", state->vendor); >>> + v4l2_info(sd, "product: %s\n", state->product); >> >> If at all possible you should log the received InfoFrames here (hdmi_infoframe_log). >> Also whether an EDID is loaded or not and the HPD state. >> >> If the hardware supports 5V detection, then you should log that too. In that case >> also implement support for the V4L2_CID_DV_RX_POWER_PRESENT control. >> >> Also any information on the signal detection (clock lock, sync lock(s), whatever). >> >> This all helps enormously when debugging problems. It's important to spend some >> time on this function. The adv7604.c source might be a good place to look for >> inspiration. > > ok - I will add what I can. > > I can't figure out how to use log-status on a subdev either: > > root@ventana:~# cat /sys/class/video4linux/v4l-subdev1/name > tda19971 2-0048 > root@ventana:~# v4l2-ctl -d /dev/v4l-subdev1 --log-status > VIDIOC_QUERYCAP: failed: Inappropriate ioctl for device > /dev/v4l-subdev1: not a v4l2 node Should be fixed once v4l-subdev support is added to v4l2-ctl. > >> >>> + >>> + return 0; >>> +} >>> + > <snip> >>> + >>> +static int tda1997x_core_init(struct v4l2_subdev *sd) >>> +{ >>> + struct tda1997x_state *state = to_state(sd); >>> + struct tda1997x_platform_data *pdata = &state->pdata; >>> + u8 reg; >>> + int i; >>> + >>> + /* disable HPD */ >>> + io_write(sd, REG_HPD_AUTO_CTRL, HPD_AUTO_HPD_UNSEL); >>> + if (state->chip_revision == 0) { >>> + io_write(sd, REG_MAN_SUS_HDMI_SEL, MAN_DIS_HDCP | MAN_RST_HDCP); >>> + io_write(sd, REG_CGU_DBG_SEL, 1 << CGU_DBG_CLK_SEL_SHIFT); >>> + } >>> + >>> + /* reset infoframe at end of start-up-sequencer */ >>> + io_write(sd, REG_SUS_SET_RGB2, 0x06); >>> + io_write(sd, REG_SUS_SET_RGB3, 0x06); >>> + >>> + /* Enable TMDS pull-ups */ >>> + io_write(sd, REG_RT_MAN_CTRL, RT_MAN_CTRL_RT | >>> + RT_MAN_CTRL_RT_B | RT_MAN_CTRL_RT_A); >>> + >>> + /* enable sync measurement timing */ >>> + tda1997x_cec_write(sd, REG_PWR_CONTROL & 0xff, 0x04); >>> + /* adjust CEC clock divider */ >>> + tda1997x_cec_write(sd, REG_OSC_DIVIDER & 0xff, 0x03); >>> + tda1997x_cec_write(sd, REG_EN_OSC_PERIOD_LSB & 0xff, 0xa0); >>> + io_write(sd, REG_TIMER_D, 0x54); >>> + /* enable power switch */ >>> + reg = tda1997x_cec_read(sd, REG_CONTROL & 0xff); >>> + reg |= 0x20; >>> + tda1997x_cec_write(sd, REG_CONTROL & 0xff, reg); >>> + mdelay(50); >>> + >>> + /* read the chip version */ >>> + reg = io_read(sd, REG_VERSION); >>> + /* get the chip configuration */ >>> + reg = io_read(sd, REG_CMTP_REG10); >>> + >>> + /* enable interrupts we care about */ >>> + io_write(sd, REG_INT_MASK_TOP, >>> + INTERRUPT_HDCP | INTERRUPT_AUDIO | INTERRUPT_INFO | >>> + INTERRUPT_RATE | INTERRUPT_SUS); >>> + /* config_mtp,fmt,sus_end,sus_st */ >>> + io_write(sd, REG_INT_MASK_SUS, MASK_MPT | MASK_FMT | MASK_SUS_END); >>> + /* rate stability change for inputs A/B */ >>> + io_write(sd, REG_INT_MASK_RATE, MASK_RATE_B_ST | MASK_RATE_A_ST); >>> + /* aud,spd,avi*/ >>> + io_write(sd, REG_INT_MASK_INFO, >>> + MASK_AUD_IF | MASK_SPD_IF | MASK_AVI_IF); >>> + /* audio_freq,audio_flg,mute_flg,fifo_err */ >>> + io_write(sd, REG_INT_MASK_AUDIO, >>> + MASK_AUDIO_FREQ_FLG | MASK_AUDIO_FLG | MASK_MUTE_FLG | >>> + MASK_ERROR_FIFO_PT); >>> + /* HDCP C5 state reached */ >>> + io_write(sd, REG_INT_MASK_HDCP, MASK_STATE_C5); >>> + /* don't care about AFE/DDC/MODE */ >>> + io_write(sd, REG_INT_MASK_AFE, 0); >>> + io_write(sd, REG_INT_MASK_DDC, 0); >>> + io_write(sd, REG_INT_MASK_MODE, 0); >>> + >>> + /* clear all interrupts */ >>> + io_write(sd, REG_INT_FLG_CLR_TOP, 0xff); >>> + io_write(sd, REG_INT_FLG_CLR_SUS, 0xff); >>> + io_write(sd, REG_INT_FLG_CLR_DDC, 0xff); >>> + io_write(sd, REG_INT_FLG_CLR_RATE, 0xff); >>> + io_write(sd, REG_INT_FLG_CLR_MODE, 0xff); >>> + io_write(sd, REG_INT_FLG_CLR_INFO, 0xff); >>> + io_write(sd, REG_INT_FLG_CLR_AUDIO, 0xff); >>> + io_write(sd, REG_INT_FLG_CLR_HDCP, 0xff); >>> + io_write(sd, REG_INT_FLG_CLR_AFE, 0xff); >>> + >>> + /* init TMDS equalizer */ >>> + if (state->chip_revision == 0) >>> + io_write(sd, REG_CGU_DBG_SEL, 1 << CGU_DBG_CLK_SEL_SHIFT); >>> + io_write24(sd, REG_CLK_MIN_RATE, CLK_MIN_RATE); >>> + io_write24(sd, REG_CLK_MAX_RATE, CLK_MAX_RATE); >>> + if (state->chip_revision == 0) >>> + io_write(sd, REG_WDL_CFG, WDL_CFG_VAL); >>> + /* DC filter */ >>> + io_write(sd, REG_DEEP_COLOR_CTRL, DC_FILTER_VAL); >>> + /* disable test pattern */ >>> + io_write(sd, REG_SVC_MODE, 0x00); >>> + /* update HDMI INFO CTRL */ >>> + io_write(sd, REG_INFO_CTRL, 0xff); >>> + /* write HDMI INFO EXCEED value */ >>> + io_write(sd, REG_INFO_EXCEED, 3); >>> + >>> + if (state->chip_revision == 0) >>> + tda1997x_reset_n1(state); >>> + >>> + /* >>> + * No HDCP acknowledge when HDCP is disabled >>> + * and reset SUS to force format detection >>> + */ >>> + tda1997x_hdmi_info_reset(sd, NACK_HDCP, true); >>> + >>> + /* Set HPD low */ >>> + tda1997x_manual_hpd(sd, HPD_LOW_BP); >>> + >>> + /* Configure receiver capabilities */ >>> + io_write(sd, REG_HDCP_BCAPS, HDCP_HDMI | HDCP_FAST_REAUTH); >>> + >>> + /* Configure HDMI: Auto HDCP mode, packet controlled mute */ >>> + reg = HDMI_CTRL_MUTE_AUTO << HDMI_CTRL_MUTE_SHIFT; >>> + reg |= HDMI_CTRL_HDCP_AUTO << HDMI_CTRL_HDCP_SHIFT; >>> + io_write(sd, REG_HDMI_CTRL, reg); >>> + >>> + /* reset start-up-sequencer to force format detection */ >>> + tda1997x_hdmi_info_reset(sd, 0, true); >>> + >>> + /* Set HPD high */ >>> + tda1997x_manual_hpd(sd, HPD_HIGH_OTHER); >>> + tda1997x_manual_hpd(sd, HPD_HIGH_BP); >> >> How can you set the HPD high if there is no EDID? No EDID, no HPD. >> > > right - I'll remove this > >>> + >>> + /* disable matrix conversion */ >>> + reg = io_read(sd, REG_VDP_CTRL); >>> + reg |= VDP_CTRL_MATRIX_BP; >>> + io_write(sd, REG_VDP_CTRL, reg); >>> + > <snip> >>> + >>> + ret = 0x34 + ((io_read(sd, REG_SLAVE_ADDR)>>4) & 0x03); >>> + state->client_cec = i2c_new_dummy(client->adapter, ret); >>> + v4l_info(client, "CEC slave address 0x%02x\n", ret); >>> + >>> + ret = tda1997x_core_init(sd); >> >> Unless I missed it, I don't think state->timings has been initialized >> to something valid. During probe the hdmi receiver has to be initialized >> to something. The API expects that. Usually VGA or 720p60 or 1080p60 is >> chosen for this. > > you didn't miss it - I didn't know exactly what to do there. > > I'll initialize it to VGA > >> >>> + if (ret) >>> + goto err_free_mutex; >>> + > <snip> >>> >> >> Regards, >> >> Hans > > Regarding video standard detection where this chip provides me with > vertical-period, horizontal-period, and horizontal-pulse-width I > should be able to detect the standard simply based off of > vertical-period (framerate) and horizontal-period (line width > including blanking) right? I wasn't sure if my method of matching > these within 14% tolerance made sense. I will be removing the hsmatch > logic from that as it seems the horizontal-pulse-width should be > irrelevant. For proper video detection you ideally need: h/v sync size h/v back/front porch size h/v polarity pixelclock (usually an approximation) The v4l2_find_dv_timings_cap() helper can help you find the corresponding timings, allowing for pixelclock variation. That function assumes that the sync/back/frontporch values are all known. But not all devices can actually discover those values. What can your hardware detect? Can it tell front and backporch apart? Can it determine the sync size? I've been considering for some time to improve that helper function to be able to handle hardware that isn't able separate sync/back/frontporch values. Regards, Hans
On Thu, Oct 19, 2017 at 12:39 AM, Hans Verkuil <hverkuil@xs4all.nl> wrote: <snip> >>> What I am missing here is handling of the RGB quantization range. >>> An HDMI receiver will typically send full range RGB or limited range YUV >>> to the SoC. The HDMI source can however send full or limited range RGB >>> or limited range YUV (full range YUV is theoretically possible, but nobody >>> does that). >>> >> >> isn't this quantization range a function of the colorspace and >> colorimetry dictated by the AVI infoframe? I'm taking these into >> consideration when setting up the conversion matrix in >> tda1997x_configure_conv(). > > No, it's independent of that. and from another reply: > A small correction here: while ideally you should indeed check if the current > EDID supports selectable RGB Quantization Range, in practice you don't need > to. If the source explicitly sets the RGB quantization range, then just use > that. > > Note: some hardware can do this automatically (adv7604) by detecting what is > transmitted in the AVI InfoFrame. That's probably not the case here since you > have to provide a conversion matrix. I see the AVI infoframe has hdmi_quantization_range and hdmi_ycc_quantization_range along with vid_code. I'm not at all clear what to do with this information. Is there anything you see in the datasheet [1] that points to something I need to be doing? > >> >>> For a Full HD receiver the rules when receiving RGB video are as follows: >>> >>> If the EDID supports selectable RGB Quantization Range, then check if the >>> source explicitly sets the RGB quantization range in the AVI InfoFrame and >>> use that value. >>> >>> Otherwise fall back to the default rules: >>> >>> if VIC == 0, then expect full range RGB, otherwise expect limited range RGB. >> >> Are you referring to the video_code field of the AVI infoframe or vic >> from a vendor infoframe? > > AVI InfoFrame. > > The HDMI VIC codes in the vendor InfoFrame are only valid for 4k formats. And > that's not supported by this device, right? Right, the TDA1997x supports 1080p only. > >> >>> >>> It gets even more complicated with 4k video, but this is full HD only. >>> >>> In addition, you may also want to implement the V4L2_CID_DV_RX_RGB_RANGE control >>> to let userspace override the autodetection. >> >> I'll add that as an additional patch. Are there other V4L2_CID's that >> I should be adding here? > > V4L2_CID_DV_RX_POWER_PRESENT (if possible) and optionally V4L2_CID_DV_RX_IT_CONTENT_TYPE. > It looks like there is a register for 5V_HPD detect. I assume the content type to return is the value reported from the AVI frame? <snip> >> >> Regarding video standard detection where this chip provides me with >> vertical-period, horizontal-period, and horizontal-pulse-width I >> should be able to detect the standard simply based off of >> vertical-period (framerate) and horizontal-period (line width >> including blanking) right? I wasn't sure if my method of matching >> these within 14% tolerance made sense. I will be removing the hsmatch >> logic from that as it seems the horizontal-pulse-width should be >> irrelevant. > > For proper video detection you ideally need: > > h/v sync size > h/v back/front porch size > h/v polarity > pixelclock (usually an approximation) > > The v4l2_find_dv_timings_cap() helper can help you find the corresponding > timings, allowing for pixelclock variation. > > That function assumes that the sync/back/frontporch values are all known. > But not all devices can actually discover those values. What can your > hardware detect? Can it tell front and backporch apart? Can it determine > the sync size? > > I've been considering for some time to improve that helper function to be > able to handle hardware that isn't able separate sync/back/frontporch values. > The TDA1997x provides only the vertical/horizontal periods and the horizontal pulse width (section 8.3.4 of datasheet [1]). Can you point me to a good primer on the relationship between these values and the h/v back/front porch? Currently I iterate over the list of known formats calculating hper (bt->pixelclock / V4L2_DV_BT_FRAME_WIDTH(bt)) and vper (hper / V4L2_DV_BT_FRAME_HEIGHT(bt)) (framerate) and find the closest match within +/- 7% tolerance. The list of supported formats is sorted by framerate then width. /* look for matching timings */ for (i = 0; i < ARRAY_SIZE(tda1997x_hdmi_modes); i++) { const struct tda1997x_video_std *std = &tda1997x_hdmi_modes[i]; const struct v4l2_bt_timings *bt = &std->timings.bt; int _hper, _vper, _hsper; int vmin, vmax, hmin, hmax, hsmin, hsmax; int vmatch, hsmatch; width = V4L2_DV_BT_FRAME_WIDTH(bt); lines = V4L2_DV_BT_FRAME_HEIGHT(bt); _hper = (int)bt->pixelclock / (int)width; _vper = _hper / lines; _hsper = (int)bt->pixelclock / (int)bt->hsync; if (bt->interlaced) _vper *= 2; /* vper +/- 0.7% */ vmin = 993 * (27000000 / _vper) / 1000; vmax = 1007 * (27000000 / _vper) / 1000; _hsper = (int)bt->pixelclock / (int)bt->hsync; if (bt->interlaced) _vper *= 2; /* vper +/- 0.7% */ vmin = 993 * (27000000 / _vper) / 1000; vmax = 1007 * (27000000 / _vper) / 1000; /* hper +/- 0.7% */ hmin = 993 * (27000000 / _hper) / 1000; hmax = 1007 * (27000000 / _hper) / 1000; /* vmatch matches the framerate */ vmatch = ((vper <= vmax) && (vper >= vmin)) ? 1 : 0; /* hmatch matches the width */ hmatch = ((hper <= hmax) && (hper >= hmin)) ? 1 : 0; if (vmatch && hsmatch) { v4l_info(state->client, "resolution: %dx%d%c@%d (%d/%d/%d) %dMHz %d\n", bt->width, bt->height, bt->interlaced?'i':'p', _vper, vper, hper, hsper, pixrate, hsmatch); state->fps = (int)bt->pixelclock / (width * lines); state->std = std; return 0; } } Note that I've thrown out any comparisons based on horizontal pulse width from my first patch as that didn't appear to fit well. So far the above works well however I do fail to recognize the following modes (using a Marshall SG4K HDMI test generator): VESA640x480P_60HZ v4l2 defines this as 4L2_DV_BT_CEA_640X480P59_94 which fails vmatch VESA1400x1050P_60HZ should match V4L2_DV_BT_DMT_1400X1050P60_RB but it calculates 59Hz and fails vmatch VESA1920x1200P_60HZ should match V4L2_DV_BT_DMT_1920X1200P60_RB but it calculates 59Hz and fails vmatch CEAVIC1440x480I_60HZ not in v4l2-dv-timings.h CEAVIC720x480P_60HZ not in v4l2-dv-timings.h (there's a 720x480p59) CEAVIC1440x576I_50HZ not in v4l2-dv-timings.h I should have mentioned in my cover letter that I only have a datasheet for this device and some fairly obfuscated vendor example code - I don't have proper register set documentation. Regards, Tim [1] http://dev.gateworks.com/datasheets/TDA19971-datasheet-rev3.pdf
On 20/10/17 16:00, Tim Harvey wrote: > On Thu, Oct 19, 2017 at 12:39 AM, Hans Verkuil <hverkuil@xs4all.nl> wrote: > <snip> >>>> What I am missing here is handling of the RGB quantization range. >>>> An HDMI receiver will typically send full range RGB or limited range YUV >>>> to the SoC. The HDMI source can however send full or limited range RGB >>>> or limited range YUV (full range YUV is theoretically possible, but nobody >>>> does that). >>>> >>> >>> isn't this quantization range a function of the colorspace and >>> colorimetry dictated by the AVI infoframe? I'm taking these into >>> consideration when setting up the conversion matrix in >>> tda1997x_configure_conv(). >> >> No, it's independent of that. > > and from another reply: >> A small correction here: while ideally you should indeed check if the current >> EDID supports selectable RGB Quantization Range, in practice you don't need >> to. If the source explicitly sets the RGB quantization range, then just use >> that. >> >> Note: some hardware can do this automatically (adv7604) by detecting what is >> transmitted in the AVI InfoFrame. That's probably not the case here since you >> have to provide a conversion matrix. > > I see the AVI infoframe has hdmi_quantization_range and > hdmi_ycc_quantization_range along with vid_code. > > I'm not at all clear what to do with this information. Is there > anything you see in the datasheet [1] that points to something I need > to be doing? You can ignore hdmi_ycc_quantization_range, it is the hdmi_quantization_range that you need to read out. The TDA can receive the following formats: RGB Full Range RGB Limited Range YUV Bt.601 (aka SMPTE 170M) YUV Rec.709 The YUV formats are always limited range. The TDA can transmit RGB and YUV to the SoC. You want RGB to be full range and YUV to be limited range. YUV can be either 601 or 709. So if the TDA transmits RGB then you need to support the following conversions: RGB Full -> RGB Full RGB Limited -> RGB Full YUV 601 -> RGB Full YUV 709 -> RGB Full And if the TDA transmits YUV then you need these conversions: RGB Full -> YUV601 or YUV709 RGB Limited -> YUV601 or YUV709 YUV601 -> YUV601 YUV709 -> YUV709 For the RGB to YUV conversion you have a choice of converting to YUV601 or 709. I recommend to either always convert to YUV601 or to let it depend on the resolution (SDTV YUV601, HDTV YUV709). Ideally the application should specify what it wants, but we don't have any API support for that. > >> >>> >>>> For a Full HD receiver the rules when receiving RGB video are as follows: >>>> >>>> If the EDID supports selectable RGB Quantization Range, then check if the >>>> source explicitly sets the RGB quantization range in the AVI InfoFrame and >>>> use that value. >>>> >>>> Otherwise fall back to the default rules: >>>> >>>> if VIC == 0, then expect full range RGB, otherwise expect limited range RGB. >>> >>> Are you referring to the video_code field of the AVI infoframe or vic >>> from a vendor infoframe? >> >> AVI InfoFrame. >> >> The HDMI VIC codes in the vendor InfoFrame are only valid for 4k formats. And >> that's not supported by this device, right? > > Right, the TDA1997x supports 1080p only. > >> >>> >>>> >>>> It gets even more complicated with 4k video, but this is full HD only. >>>> >>>> In addition, you may also want to implement the V4L2_CID_DV_RX_RGB_RANGE control >>>> to let userspace override the autodetection. >>> >>> I'll add that as an additional patch. Are there other V4L2_CID's that >>> I should be adding here? >> >> V4L2_CID_DV_RX_POWER_PRESENT (if possible) and optionally V4L2_CID_DV_RX_IT_CONTENT_TYPE. >> > > It looks like there is a register for 5V_HPD detect. > > I assume the content type to return is the value reported from the AVI frame? Correct. > > <snip> >>> >>> Regarding video standard detection where this chip provides me with >>> vertical-period, horizontal-period, and horizontal-pulse-width I >>> should be able to detect the standard simply based off of >>> vertical-period (framerate) and horizontal-period (line width >>> including blanking) right? I wasn't sure if my method of matching >>> these within 14% tolerance made sense. I will be removing the hsmatch >>> logic from that as it seems the horizontal-pulse-width should be >>> irrelevant. >> >> For proper video detection you ideally need: >> >> h/v sync size >> h/v back/front porch size >> h/v polarity >> pixelclock (usually an approximation) >> >> The v4l2_find_dv_timings_cap() helper can help you find the corresponding >> timings, allowing for pixelclock variation. >> >> That function assumes that the sync/back/frontporch values are all known. >> But not all devices can actually discover those values. What can your >> hardware detect? Can it tell front and backporch apart? Can it determine >> the sync size? >> >> I've been considering for some time to improve that helper function to be >> able to handle hardware that isn't able separate sync/back/frontporch values. >> > > The TDA1997x provides only the vertical/horizontal periods and the > horizontal pulse > width (section 8.3.4 of datasheet [1]). > > Can you point me to a good primer on the relationship between these > values and the h/v back/front porch? The blanking consists of a front porch width, a sync width and a back porch width. 'Width' is normally measured in pixels. Vertical blanking is the same, except that is measured in lines. All these values are defined in the standards that define these timings (e.g. VESA DMT, CTA-861). So for 1080p60 the active video is 1920x1080. After each line of 1920 pixels you have 88 'pixels' of front porch, a sync pulse of 44 'pixels' and a back porch of 148 pixels. Total frame width is 2200. Similar for the vertical. A good HDMI receiver will give you the exact values for these blanking sizes. Especially the sync width/height is important since that can provide additional format information when receiving VESA formats. It looks as if the TDA does not measure in exact pixels but in 27 MHz clock periods. Which is an approximation only. So it appears that what you do is the best you can do. Although I wonder about the hsper: the datasheet suggests that this is the width, not a period. What is the value you read out when you receive 1080p60? If that would be an exact width, then that would help a lot since you can compare that against bt->hsync. > > Currently I iterate over the list of known formats calculating hper > (bt->pixelclock / V4L2_DV_BT_FRAME_WIDTH(bt)) and vper (hper / > V4L2_DV_BT_FRAME_HEIGHT(bt)) (framerate) and find the closest match > within +/- 7% tolerance. The list of supported formats is sorted by > framerate then width. > > /* look for matching timings */ > for (i = 0; i < ARRAY_SIZE(tda1997x_hdmi_modes); i++) { > const struct tda1997x_video_std *std = &tda1997x_hdmi_modes[i]; > const struct v4l2_bt_timings *bt = &std->timings.bt; > int _hper, _vper, _hsper; > int vmin, vmax, hmin, hmax, hsmin, hsmax; > int vmatch, hsmatch; > > width = V4L2_DV_BT_FRAME_WIDTH(bt); > lines = V4L2_DV_BT_FRAME_HEIGHT(bt); > > _hper = (int)bt->pixelclock / (int)width; > _vper = _hper / lines; > _hsper = (int)bt->pixelclock / (int)bt->hsync; > if (bt->interlaced) > _vper *= 2; > /* vper +/- 0.7% */ > vmin = 993 * (27000000 / _vper) / 1000; > vmax = 1007 * (27000000 / _vper) / 1000; > _hsper = (int)bt->pixelclock / (int)bt->hsync; > if (bt->interlaced) > _vper *= 2; > /* vper +/- 0.7% */ > vmin = 993 * (27000000 / _vper) / 1000; > vmax = 1007 * (27000000 / _vper) / 1000; > /* hper +/- 0.7% */ > hmin = 993 * (27000000 / _hper) / 1000; > hmax = 1007 * (27000000 / _hper) / 1000; > > /* vmatch matches the framerate */ > vmatch = ((vper <= vmax) && (vper >= vmin)) ? 1 : 0; > /* hmatch matches the width */ > hmatch = ((hper <= hmax) && (hper >= hmin)) ? 1 : 0; > if (vmatch && hsmatch) { > v4l_info(state->client, > "resolution: %dx%d%c@%d (%d/%d/%d) %dMHz %d\n", > bt->width, bt->height, bt->interlaced?'i':'p', > _vper, vper, hper, hsper, pixrate, hsmatch); > state->fps = (int)bt->pixelclock / (width * lines); > state->std = std; > return 0; > } > } > > Note that I've thrown out any comparisons based on horizontal pulse > width from my first patch as that didn't appear to fit well. So far > the above works well however I do fail to recognize the following > modes (using a Marshall SG4K HDMI test generator): > > VESA640x480P_60HZ v4l2 defines this as 4L2_DV_BT_CEA_640X480P59_94 > which fails vmatch > VESA1400x1050P_60HZ should match V4L2_DV_BT_DMT_1400X1050P60_RB but it > calculates 59Hz and fails vmatch > VESA1920x1200P_60HZ should match V4L2_DV_BT_DMT_1920X1200P60_RB but it > calculates 59Hz and fails vmatch > CEAVIC1440x480I_60HZ not in v4l2-dv-timings.h This is a pixel-repeat format. They aren't defined in v4l2-dv-timings. I never came across them in real life, so that's why I never bothered. > CEAVIC720x480P_60HZ not in v4l2-dv-timings.h (there's a 720x480p59) They are the same. > CEAVIC1440x576I_50HZ not in v4l2-dv-timings.h > > I should have mentioned in my cover letter that I only have a > datasheet for this device and some fairly obfuscated vendor example > code - I don't have proper register set documentation. > > Regards, > > Tim > > [1] http://dev.gateworks.com/datasheets/TDA19971-datasheet-rev3.pdf > Regards, Hans
On Fri, Oct 20, 2017 at 9:23 AM, Hans Verkuil <hverkuil@xs4all.nl> wrote: >> >> I see the AVI infoframe has hdmi_quantization_range and >> hdmi_ycc_quantization_range along with vid_code. >> >> I'm not at all clear what to do with this information. Is there >> anything you see in the datasheet [1] that points to something I need >> to be doing? > > You can ignore hdmi_ycc_quantization_range, it is the hdmi_quantization_range > that you need to read out. > > The TDA can receive the following formats: > > RGB Full Range > RGB Limited Range > YUV Bt.601 (aka SMPTE 170M) > YUV Rec.709 > > The YUV formats are always limited range. > > The TDA can transmit RGB and YUV to the SoC. You want RGB to be full range and > YUV to be limited range. YUV can be either 601 or 709. > > So if the TDA transmits RGB then you need to support the following conversions: > > RGB Full -> RGB Full > RGB Limited -> RGB Full > YUV 601 -> RGB Full > YUV 709 -> RGB Full > > And if the TDA transmits YUV then you need these conversions: > > RGB Full -> YUV601 or YUV709 > RGB Limited -> YUV601 or YUV709 > YUV601 -> YUV601 > YUV709 -> YUV709 > > For the RGB to YUV conversion you have a choice of converting to YUV601 or 709. > I recommend to either always convert to YUV601 or to let it depend on the resolution > (SDTV YUV601, HDTV YUV709). > Ok - this is a good explanation that I should be able to follow. I will make sure to take into account hdmi_quantization_range when I setup the colorspace conversion matrix for v3. > Ideally the application should specify what it wants, but we don't have any API > support for that. > <snip> >> >> The TDA1997x provides only the vertical/horizontal periods and the >> horizontal pulse >> width (section 8.3.4 of datasheet [1]). >> >> Can you point me to a good primer on the relationship between these >> values and the h/v back/front porch? > > The blanking consists of a front porch width, a sync width and a back porch width. > 'Width' is normally measured in pixels. Vertical blanking is the same, except that > is measured in lines. All these values are defined in the standards that define these > timings (e.g. VESA DMT, CTA-861). > > So for 1080p60 the active video is 1920x1080. After each line of 1920 pixels you > have 88 'pixels' of front porch, a sync pulse of 44 'pixels' and a back porch of > 148 pixels. Total frame width is 2200. Similar for the vertical. > > A good HDMI receiver will give you the exact values for these blanking sizes. > Especially the sync width/height is important since that can provide additional > format information when receiving VESA formats. > > It looks as if the TDA does not measure in exact pixels but in 27 MHz clock > periods. Which is an approximation only. > > So it appears that what you do is the best you can do. > > Although I wonder about the hsper: the datasheet suggests that this is the width, > not a period. What is the value you read out when you receive 1080p60? If that > would be an exact width, then that would help a lot since you can compare that > against bt->hsync. Here's a list of source modes and the vertical/horizontal period and horizontal pulse width returned from the TDA: 00: VESA640x480P_60HZ 450427 856 101 01: VESA800x600P_60HZ 447620 711 85 02: VESA1024x768P_60HZ 449952 557 55 03: VESA1280x768P_60HZ 450021 568 11 04: VESA1360x768P_60HZ 449867 564 34 05: VESA1280x960P_60HZ 449981 448 55 06: VESA1280x1024P_60HZ 449833 420 27 07: VESA1400x1050P_60HZ 450372 416 7 08: VESA1600x1200P_60HZ 449981 358 27 09: VESA1920x1200P_60HZ 450355 363 4 10: CEAVIC1440x480I_60HZ 450430 1714 123 11: CEAVIC720x480P_60HZ 450430 856 61 12: CEAVIC1280x720P_60HZ 449981 598 13 13: CEAVIC1280x720P_59.94 450431 599 13 14: CEAVIC1920x1080I_60HZ 449981 798 15 15: CEAVIC1920x1080I_59.95HZ 450431 799 15 16: CEAVIC1920x1080P_30HZ 899962 798 15 17: CEAVIC1920x1080P_29.95HZ 900862 799 15 18: CEAVIC1920x1080P_24HZ 1124953 998 15 19: CEAVIC1920x1080P_23.976HZ 1126077 999 15 20: CEAVIC1920x1080P_60HZ 449981 398 7 21: CEAVIC1920x1080P_59.94HZ 450431 399 7 22: CEAVIC1440x576I_50HZ 539976 1726 127 23: CEAVIC720x576P_50HZ 539976 862 63 24: CEAVIC1280x720P_50HZ 539977 718 13 25: CEAVIC1920x1080I_50HZ 539977 958 15 26: CEAVIC1920x1080P_25HZ 1079954 958 15 27: CEAVIC1920x1080P_50HZ 539977 478 7 so 1080p60 gives a hswidth=7 Tim
On Mon, Oct 23, 2017 at 10:05 AM, Tim Harvey <tharvey@gateworks.com> wrote: > > On Fri, Oct 20, 2017 at 9:23 AM, Hans Verkuil <hverkuil@xs4all.nl> wrote: > > >> > >> I see the AVI infoframe has hdmi_quantization_range and > >> hdmi_ycc_quantization_range along with vid_code. > >> > >> I'm not at all clear what to do with this information. Is there > >> anything you see in the datasheet [1] that points to something I need > >> to be doing? > > > > You can ignore hdmi_ycc_quantization_range, it is the hdmi_quantization_range > > that you need to read out. > > > > The TDA can receive the following formats: > > > > RGB Full Range > > RGB Limited Range > > YUV Bt.601 (aka SMPTE 170M) > > YUV Rec.709 > > > > The YUV formats are always limited range. > > > > The TDA can transmit RGB and YUV to the SoC. You want RGB to be full range and > > YUV to be limited range. YUV can be either 601 or 709. > > > > So if the TDA transmits RGB then you need to support the following conversions: > > > > RGB Full -> RGB Full > > RGB Limited -> RGB Full > > YUV 601 -> RGB Full > > YUV 709 -> RGB Full > > > > And if the TDA transmits YUV then you need these conversions: > > > > RGB Full -> YUV601 or YUV709 > > RGB Limited -> YUV601 or YUV709 > > YUV601 -> YUV601 > > YUV709 -> YUV709 > > > > For the RGB to YUV conversion you have a choice of converting to YUV601 or 709. > > I recommend to either always convert to YUV601 or to let it depend on the resolution > > (SDTV YUV601, HDTV YUV709). > > > > Ok - this is a good explanation that I should be able to follow. I > will make sure to take into account hdmi_quantization_range when I > setup the colorspace conversion matrix for v3. Hans, I'm having trouble figuring out the conversion matrix to use between limited and full. Currently I have the following conversion matrices, the values which came from some old vendor code: /* Colorspace conversion matrix coefficients and offsets */ struct color_matrix_coefs { /* 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; }; /* Conversion matrixes */ enum { ITU709_RGBLIMITED, ITU601_RGBLIMITED, RGBLIMITED_ITU601, }; static const struct color_matrix_coefs conv_matrix[] = { /* ITU709 -> RGBLimited */ { -256, -2048, -2048, 4096, -1875, -750, 4096, 6307, 0, 4096, 0, 7431, 256, 256, 256, }, /* YUV601 limited -> RGB limited */ { -256, -2048, -2048, 4096, -2860, -1378, 4096, 5615, 0, 4096, 0, 7097, 256, 256, 256, }, /* RGB limited -> ITU601 */ { -256, -256, -256, 2404, 1225, 467, -1754, 2095, -341, -1388, -707, 2095, 256, 2048, 2048, }, }; Assuming the above are correct this leaves me missing RGB limitted -> RGB full, YUV601 -> RGB full, YUV709 -> RGB full, and RGB Full -> YUV601. I don't have documentation for the registers but I'm assuming the input offset is applied first, then the multiplication by the coef, then the output offset is applied. I'm looking over https://en.wikipedia.org/wiki/YUV for colorspace conversion matrices but I'm unable to figure out how to apply those to the above. Any ideas? Thanks, Tim
On 11/04/2017 01:17 AM, Tim Harvey wrote: > On Mon, Oct 23, 2017 at 10:05 AM, Tim Harvey <tharvey@gateworks.com> wrote: >> >> On Fri, Oct 20, 2017 at 9:23 AM, Hans Verkuil <hverkuil@xs4all.nl> wrote: >> >>>> >>>> I see the AVI infoframe has hdmi_quantization_range and >>>> hdmi_ycc_quantization_range along with vid_code. >>>> >>>> I'm not at all clear what to do with this information. Is there >>>> anything you see in the datasheet [1] that points to something I need >>>> to be doing? >>> >>> You can ignore hdmi_ycc_quantization_range, it is the hdmi_quantization_range >>> that you need to read out. >>> >>> The TDA can receive the following formats: >>> >>> RGB Full Range >>> RGB Limited Range >>> YUV Bt.601 (aka SMPTE 170M) >>> YUV Rec.709 >>> >>> The YUV formats are always limited range. >>> >>> The TDA can transmit RGB and YUV to the SoC. You want RGB to be full range and >>> YUV to be limited range. YUV can be either 601 or 709. >>> >>> So if the TDA transmits RGB then you need to support the following conversions: >>> >>> RGB Full -> RGB Full >>> RGB Limited -> RGB Full >>> YUV 601 -> RGB Full >>> YUV 709 -> RGB Full >>> >>> And if the TDA transmits YUV then you need these conversions: >>> >>> RGB Full -> YUV601 or YUV709 >>> RGB Limited -> YUV601 or YUV709 >>> YUV601 -> YUV601 >>> YUV709 -> YUV709 >>> >>> For the RGB to YUV conversion you have a choice of converting to YUV601 or 709. >>> I recommend to either always convert to YUV601 or to let it depend on the resolution >>> (SDTV YUV601, HDTV YUV709). >>> >> >> Ok - this is a good explanation that I should be able to follow. I >> will make sure to take into account hdmi_quantization_range when I >> setup the colorspace conversion matrix for v3. > > Hans, > > I'm having trouble figuring out the conversion matrix to use between > limited and full. > > Currently I have the following conversion matrices, the values which > came from some old vendor code: > > /* Colorspace conversion matrix coefficients and offsets */ > struct color_matrix_coefs { > /* 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; > }; > /* Conversion matrixes */ > enum { > ITU709_RGBLIMITED, > ITU601_RGBLIMITED, > RGBLIMITED_ITU601, > }; > static const struct color_matrix_coefs conv_matrix[] = { > /* ITU709 -> RGBLimited */ > { > -256, -2048, -2048, > 4096, -1875, -750, > 4096, 6307, 0, > 4096, 0, 7431, > 256, 256, 256, > }, > /* YUV601 limited -> RGB limited */ > { > -256, -2048, -2048, > 4096, -2860, -1378, > 4096, 5615, 0, > 4096, 0, 7097, > 256, 256, 256, > }, > /* RGB limited -> ITU601 */ > { > -256, -256, -256, > 2404, 1225, 467, > -1754, 2095, -341, > -1388, -707, 2095, > 256, 2048, 2048, > }, > }; > > Assuming the above are correct this leaves me missing RGB limitted -> > RGB full, YUV601 -> RGB full, YUV709 -> RGB full, and RGB Full -> > YUV601. > > I don't have documentation for the registers but I'm assuming the > input offset is applied first, then the multiplication by the coef, > then the output offset is applied. I'm looking over > https://en.wikipedia.org/wiki/YUV for colorspace conversion matrices > but I'm unable to figure out how to apply those to the above. Any > ideas? For the YUV to RGB full conversions all you need to do is to change the last row to 0, 0, 0 (since you no longer apply an offset) and multiply all matrix coefficients by (255 / 219) to ensure the matrix result is in the range [0-255] instead of [0-219]. For the RGB lim to RGB full conversion you need this matrix: -256, -256, -256, S, 0, 0, 0, S, 0, 0, 0, S, 0, 0, 0 Where S = 4096 * 255 / 219. I'm assuming 4096 equals 1.0 using fixed point format. Actually, looking at the order of the matrix values I suspect it might be: -256, -256, -256, 0, S, 0, 0, 0, S, S, 0, 0, 0, 0, 0 You'll have to test this to verify which of the two is the right one. For RGB Full to YUV you use the RGB lim to YUV values but replace the first line by 0, 0, 0 and multiply the 3x3 matrix values with (219 / 255). That should do it. Regards, Hans
On Fri, Oct 20, 2017 at 7:00 AM, Tim Harvey <tharvey@gateworks.com> wrote: > On Thu, Oct 19, 2017 at 12:39 AM, Hans Verkuil <hverkuil@xs4all.nl> wrote: > <snip> >>> >>> Regarding video standard detection where this chip provides me with >>> vertical-period, horizontal-period, and horizontal-pulse-width I >>> should be able to detect the standard simply based off of >>> vertical-period (framerate) and horizontal-period (line width >>> including blanking) right? I wasn't sure if my method of matching >>> these within 14% tolerance made sense. I will be removing the hsmatch >>> logic from that as it seems the horizontal-pulse-width should be >>> irrelevant. >> >> For proper video detection you ideally need: >> >> h/v sync size >> h/v back/front porch size >> h/v polarity >> pixelclock (usually an approximation) >> >> The v4l2_find_dv_timings_cap() helper can help you find the corresponding >> timings, allowing for pixelclock variation. >> >> That function assumes that the sync/back/frontporch values are all known. >> But not all devices can actually discover those values. What can your >> hardware detect? Can it tell front and backporch apart? Can it determine >> the sync size? >> >> I've been considering for some time to improve that helper function to be >> able to handle hardware that isn't able separate sync/back/frontporch values. >> > > The TDA1997x provides only the vertical/horizontal periods and the > horizontal pulse > width (section 8.3.4 of datasheet [1]). > > Can you point me to a good primer on the relationship between these > values and the h/v back/front porch? > > Currently I iterate over the list of known formats calculating hper > (bt->pixelclock / V4L2_DV_BT_FRAME_WIDTH(bt)) and vper (hper / > V4L2_DV_BT_FRAME_HEIGHT(bt)) (framerate) and find the closest match > within +/- 7% tolerance. The list of supported formats is sorted by > framerate then width. > > /* look for matching timings */ > for (i = 0; i < ARRAY_SIZE(tda1997x_hdmi_modes); i++) { > const struct tda1997x_video_std *std = &tda1997x_hdmi_modes[i]; > const struct v4l2_bt_timings *bt = &std->timings.bt; > int _hper, _vper, _hsper; > int vmin, vmax, hmin, hmax, hsmin, hsmax; > int vmatch, hsmatch; > > width = V4L2_DV_BT_FRAME_WIDTH(bt); > lines = V4L2_DV_BT_FRAME_HEIGHT(bt); > > _hper = (int)bt->pixelclock / (int)width; > _vper = _hper / lines; > _hsper = (int)bt->pixelclock / (int)bt->hsync; > if (bt->interlaced) > _vper *= 2; > /* vper +/- 0.7% */ > vmin = 993 * (27000000 / _vper) / 1000; > vmax = 1007 * (27000000 / _vper) / 1000; > _hsper = (int)bt->pixelclock / (int)bt->hsync; > if (bt->interlaced) > _vper *= 2; > /* vper +/- 0.7% */ > vmin = 993 * (27000000 / _vper) / 1000; > vmax = 1007 * (27000000 / _vper) / 1000; > /* hper +/- 0.7% */ > hmin = 993 * (27000000 / _hper) / 1000; > hmax = 1007 * (27000000 / _hper) / 1000; > > /* vmatch matches the framerate */ > vmatch = ((vper <= vmax) && (vper >= vmin)) ? 1 : 0; > /* hmatch matches the width */ > hmatch = ((hper <= hmax) && (hper >= hmin)) ? 1 : 0; > if (vmatch && hsmatch) { > v4l_info(state->client, > "resolution: %dx%d%c@%d (%d/%d/%d) %dMHz %d\n", > bt->width, bt->height, bt->interlaced?'i':'p', > _vper, vper, hper, hsper, pixrate, hsmatch); > state->fps = (int)bt->pixelclock / (width * lines); > state->std = std; > return 0; > } > } > > Note that I've thrown out any comparisons based on horizontal pulse > width from my first patch as that didn't appear to fit well. So far > the above works well however I do fail to recognize the following > modes (using a Marshall SG4K HDMI test generator): > Hans, I've found that I do indeed need to look at the 'hsper' that the TDA provides above along with the vper/hper as there are several timings that match a given vper/hper. However I haven't figured out how to make sense of the hsper value that is returned. Here are some example timings and the vper/hper/hsper returned from the TDA: V4L2_DV_BT_DMT_1280X960P60 449981/448/55 V4L2_DV_BT_DMT_1280X1024P60 449833/420/27 V4L2_DV_BT_DMT_1280X768P60 450021/568/11 V4L2_DV_BT_DMT_1360X768P60 449867/564/34 Do you know what the hsper could be here? It doesn't appear to match v4l2_bt_timings hsync ((27MHz/bt->pixelclock)*bt->hsync). Thanks, Tim
On 11/07/2017 07:04 AM, Tim Harvey wrote: > On Fri, Oct 20, 2017 at 7:00 AM, Tim Harvey <tharvey@gateworks.com> wrote: >> On Thu, Oct 19, 2017 at 12:39 AM, Hans Verkuil <hverkuil@xs4all.nl> wrote: >> <snip> >>>> >>>> Regarding video standard detection where this chip provides me with >>>> vertical-period, horizontal-period, and horizontal-pulse-width I >>>> should be able to detect the standard simply based off of >>>> vertical-period (framerate) and horizontal-period (line width >>>> including blanking) right? I wasn't sure if my method of matching >>>> these within 14% tolerance made sense. I will be removing the hsmatch >>>> logic from that as it seems the horizontal-pulse-width should be >>>> irrelevant. >>> >>> For proper video detection you ideally need: >>> >>> h/v sync size >>> h/v back/front porch size >>> h/v polarity >>> pixelclock (usually an approximation) >>> >>> The v4l2_find_dv_timings_cap() helper can help you find the corresponding >>> timings, allowing for pixelclock variation. >>> >>> That function assumes that the sync/back/frontporch values are all known. >>> But not all devices can actually discover those values. What can your >>> hardware detect? Can it tell front and backporch apart? Can it determine >>> the sync size? >>> >>> I've been considering for some time to improve that helper function to be >>> able to handle hardware that isn't able separate sync/back/frontporch values. >>> >> >> The TDA1997x provides only the vertical/horizontal periods and the >> horizontal pulse >> width (section 8.3.4 of datasheet [1]). >> >> Can you point me to a good primer on the relationship between these >> values and the h/v back/front porch? >> >> Currently I iterate over the list of known formats calculating hper >> (bt->pixelclock / V4L2_DV_BT_FRAME_WIDTH(bt)) and vper (hper / >> V4L2_DV_BT_FRAME_HEIGHT(bt)) (framerate) and find the closest match >> within +/- 7% tolerance. The list of supported formats is sorted by >> framerate then width. >> >> /* look for matching timings */ >> for (i = 0; i < ARRAY_SIZE(tda1997x_hdmi_modes); i++) { >> const struct tda1997x_video_std *std = &tda1997x_hdmi_modes[i]; >> const struct v4l2_bt_timings *bt = &std->timings.bt; >> int _hper, _vper, _hsper; >> int vmin, vmax, hmin, hmax, hsmin, hsmax; >> int vmatch, hsmatch; >> >> width = V4L2_DV_BT_FRAME_WIDTH(bt); >> lines = V4L2_DV_BT_FRAME_HEIGHT(bt); >> >> _hper = (int)bt->pixelclock / (int)width; >> _vper = _hper / lines; >> _hsper = (int)bt->pixelclock / (int)bt->hsync; >> if (bt->interlaced) >> _vper *= 2; >> /* vper +/- 0.7% */ >> vmin = 993 * (27000000 / _vper) / 1000; >> vmax = 1007 * (27000000 / _vper) / 1000; >> _hsper = (int)bt->pixelclock / (int)bt->hsync; >> if (bt->interlaced) >> _vper *= 2; >> /* vper +/- 0.7% */ >> vmin = 993 * (27000000 / _vper) / 1000; >> vmax = 1007 * (27000000 / _vper) / 1000; >> /* hper +/- 0.7% */ >> hmin = 993 * (27000000 / _hper) / 1000; >> hmax = 1007 * (27000000 / _hper) / 1000; >> >> /* vmatch matches the framerate */ >> vmatch = ((vper <= vmax) && (vper >= vmin)) ? 1 : 0; >> /* hmatch matches the width */ >> hmatch = ((hper <= hmax) && (hper >= hmin)) ? 1 : 0; >> if (vmatch && hsmatch) { >> v4l_info(state->client, >> "resolution: %dx%d%c@%d (%d/%d/%d) %dMHz %d\n", >> bt->width, bt->height, bt->interlaced?'i':'p', >> _vper, vper, hper, hsper, pixrate, hsmatch); >> state->fps = (int)bt->pixelclock / (width * lines); >> state->std = std; >> return 0; >> } >> } >> >> Note that I've thrown out any comparisons based on horizontal pulse >> width from my first patch as that didn't appear to fit well. So far >> the above works well however I do fail to recognize the following >> modes (using a Marshall SG4K HDMI test generator): >> > > Hans, > > I've found that I do indeed need to look at the 'hsper' that the TDA > provides above along with the vper/hper as there are several timings > that match a given vper/hper. However I haven't figured out how to > make sense of the hsper value that is returned. > > Here are some example timings and the vper/hper/hsper returned from the TDA: > V4L2_DV_BT_DMT_1280X960P60 449981/448/55 > V4L2_DV_BT_DMT_1280X1024P60 449833/420/27 > V4L2_DV_BT_DMT_1280X768P60 450021/568/11 > V4L2_DV_BT_DMT_1360X768P60 449867/564/34 > > Do you know what the hsper could be here? It doesn't appear to match > v4l2_bt_timings hsync ((27MHz/bt->pixelclock)*bt->hsync). Actually, all numbers except for the first match (assuming V4L2_DV_BT_DMT_1280X768P60 is really V4L2_DV_BT_DMT_1280X768P60_RB). Are you sure about the first one? Unfortunately, due to rounding errors the hsper is simply not accurate enough to use reliably. Furthermore, what is really needed if you want to add support for GTF and CVT standards is the vsync value, and that's not reported at all. I'd just give up on this and use your original code. Very poor hardware design :-( Regards, Hans
On Mon, Nov 6, 2017 at 11:47 PM, Hans Verkuil <hverkuil@xs4all.nl> wrote: > On 11/07/2017 07:04 AM, Tim Harvey wrote: >> On Fri, Oct 20, 2017 at 7:00 AM, Tim Harvey <tharvey@gateworks.com> wrote: >>> On Thu, Oct 19, 2017 at 12:39 AM, Hans Verkuil <hverkuil@xs4all.nl> wrote: >>> <snip> >>>>> >>>>> Regarding video standard detection where this chip provides me with >>>>> vertical-period, horizontal-period, and horizontal-pulse-width I >>>>> should be able to detect the standard simply based off of >>>>> vertical-period (framerate) and horizontal-period (line width >>>>> including blanking) right? I wasn't sure if my method of matching >>>>> these within 14% tolerance made sense. I will be removing the hsmatch >>>>> logic from that as it seems the horizontal-pulse-width should be >>>>> irrelevant. >>>> >>>> For proper video detection you ideally need: >>>> >>>> h/v sync size >>>> h/v back/front porch size >>>> h/v polarity >>>> pixelclock (usually an approximation) >>>> >>>> The v4l2_find_dv_timings_cap() helper can help you find the corresponding >>>> timings, allowing for pixelclock variation. >>>> >>>> That function assumes that the sync/back/frontporch values are all known. >>>> But not all devices can actually discover those values. What can your >>>> hardware detect? Can it tell front and backporch apart? Can it determine >>>> the sync size? >>>> >>>> I've been considering for some time to improve that helper function to be >>>> able to handle hardware that isn't able separate sync/back/frontporch values. >>>> >>> >>> The TDA1997x provides only the vertical/horizontal periods and the >>> horizontal pulse >>> width (section 8.3.4 of datasheet [1]). >>> >>> Can you point me to a good primer on the relationship between these >>> values and the h/v back/front porch? >>> >>> Currently I iterate over the list of known formats calculating hper >>> (bt->pixelclock / V4L2_DV_BT_FRAME_WIDTH(bt)) and vper (hper / >>> V4L2_DV_BT_FRAME_HEIGHT(bt)) (framerate) and find the closest match >>> within +/- 7% tolerance. The list of supported formats is sorted by >>> framerate then width. >>> >>> /* look for matching timings */ >>> for (i = 0; i < ARRAY_SIZE(tda1997x_hdmi_modes); i++) { >>> const struct tda1997x_video_std *std = &tda1997x_hdmi_modes[i]; >>> const struct v4l2_bt_timings *bt = &std->timings.bt; >>> int _hper, _vper, _hsper; >>> int vmin, vmax, hmin, hmax, hsmin, hsmax; >>> int vmatch, hsmatch; >>> >>> width = V4L2_DV_BT_FRAME_WIDTH(bt); >>> lines = V4L2_DV_BT_FRAME_HEIGHT(bt); >>> >>> _hper = (int)bt->pixelclock / (int)width; >>> _vper = _hper / lines; >>> _hsper = (int)bt->pixelclock / (int)bt->hsync; >>> if (bt->interlaced) >>> _vper *= 2; >>> /* vper +/- 0.7% */ >>> vmin = 993 * (27000000 / _vper) / 1000; >>> vmax = 1007 * (27000000 / _vper) / 1000; >>> _hsper = (int)bt->pixelclock / (int)bt->hsync; >>> if (bt->interlaced) >>> _vper *= 2; >>> /* vper +/- 0.7% */ >>> vmin = 993 * (27000000 / _vper) / 1000; >>> vmax = 1007 * (27000000 / _vper) / 1000; >>> /* hper +/- 0.7% */ >>> hmin = 993 * (27000000 / _hper) / 1000; >>> hmax = 1007 * (27000000 / _hper) / 1000; >>> >>> /* vmatch matches the framerate */ >>> vmatch = ((vper <= vmax) && (vper >= vmin)) ? 1 : 0; >>> /* hmatch matches the width */ >>> hmatch = ((hper <= hmax) && (hper >= hmin)) ? 1 : 0; >>> if (vmatch && hsmatch) { >>> v4l_info(state->client, >>> "resolution: %dx%d%c@%d (%d/%d/%d) %dMHz %d\n", >>> bt->width, bt->height, bt->interlaced?'i':'p', >>> _vper, vper, hper, hsper, pixrate, hsmatch); >>> state->fps = (int)bt->pixelclock / (width * lines); >>> state->std = std; >>> return 0; >>> } >>> } >>> >>> Note that I've thrown out any comparisons based on horizontal pulse >>> width from my first patch as that didn't appear to fit well. So far >>> the above works well however I do fail to recognize the following >>> modes (using a Marshall SG4K HDMI test generator): >>> >> >> Hans, >> >> I've found that I do indeed need to look at the 'hsper' that the TDA >> provides above along with the vper/hper as there are several timings >> that match a given vper/hper. However I haven't figured out how to >> make sense of the hsper value that is returned. >> >> Here are some example timings and the vper/hper/hsper returned from the TDA: >> V4L2_DV_BT_DMT_1280X960P60 449981/448/55 >> V4L2_DV_BT_DMT_1280X1024P60 449833/420/27 >> V4L2_DV_BT_DMT_1280X768P60 450021/568/11 >> V4L2_DV_BT_DMT_1360X768P60 449867/564/34 >> >> Do you know what the hsper could be here? It doesn't appear to match >> v4l2_bt_timings hsync ((27MHz/bt->pixelclock)*bt->hsync). > > Actually, all numbers except for the first match (assuming V4L2_DV_BT_DMT_1280X768P60 > is really V4L2_DV_BT_DMT_1280X768P60_RB). Hans, These are actual timings that I'm feeding the TDA input from a Marshall V-SG4K-HDI HDMI test generator so they really are different timings and those are the vper/hper/hsper the TDA reports for them. Also, when I look at the original vendor code which had pre-calculated min/max numbers for vper/hper/hsper it matches the numbers above so I don't think the HDMI test generator is in error. Tim > > Are you sure about the first one? > > Unfortunately, due to rounding errors the hsper is simply not accurate enough to > use reliably. Furthermore, what is really needed if you want to add support for > GTF and CVT standards is the vsync value, and that's not reported at all. > > I'd just give up on this and use your original code. > > Very poor hardware design :-( > > Regards, > > Hans
diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 9415389..c2b0400 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -56,6 +56,15 @@ config VIDEO_TDA9840 To compile this driver as a module, choose M here: the module will be called tda9840. +config VIDEO_TDA1997X + tristate "NXP TDA1997x HDMI receiver" + depends on VIDEO_V4L2 && I2C && VIDEO_V4L2_SUBDEV_API + ---help--- + V4L2 subdevice driver for the NXP TDA1997x HDMI receivers. + + To compile this driver as a module, choose M here: the + module will be called tda1997x. + config VIDEO_TEA6415C tristate "Philips TEA6415C audio processor" depends on I2C diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index c843c18..58f2b2e 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -12,6 +12,7 @@ obj-$(CONFIG_VIDEO_TVAUDIO) += tvaudio.o obj-$(CONFIG_VIDEO_TDA7432) += tda7432.o obj-$(CONFIG_VIDEO_SAA6588) += saa6588.o obj-$(CONFIG_VIDEO_TDA9840) += tda9840.o +obj-$(CONFIG_VIDEO_TDA1997X) += tda1997x.o obj-$(CONFIG_VIDEO_TEA6415C) += tea6415c.o obj-$(CONFIG_VIDEO_TEA6420) += tea6420.o obj-$(CONFIG_VIDEO_SAA7110) += saa7110.o diff --git a/drivers/media/i2c/tda1997x.c b/drivers/media/i2c/tda1997x.c new file mode 100644 index 0000000..bf06684 --- /dev/null +++ b/drivers/media/i2c/tda1997x.c @@ -0,0 +1,3336 @@ +/* + * Copyright (C) 2017 Gateworks Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ +#include <linux/delay.h> +#include <linux/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> + +/* debug level */ +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "debug level (0-2)"); + +/* Page 0x00 - General Control */ +#define REG_VERSION 0x0000 +#define REG_INPUT_SEL 0x0001 +#define REG_SVC_MODE 0x0002 +#define REG_HPD_MAN_CTRL 0x0003 +#define REG_RT_MAN_CTRL 0x0004 +#define REG_STANDBY_SOFT_RST 0x000A +#define REG_HDMI_SOFT_RST 0x000B +#define REG_HDMI_INFO_RST 0x000C +#define REG_INT_FLG_CLR_TOP 0x000E +#define REG_INT_FLG_CLR_SUS 0x000F +#define REG_INT_FLG_CLR_DDC 0x0010 +#define REG_INT_FLG_CLR_RATE 0x0011 +#define REG_INT_FLG_CLR_MODE 0x0012 +#define REG_INT_FLG_CLR_INFO 0x0013 +#define REG_INT_FLG_CLR_AUDIO 0x0014 +#define REG_INT_FLG_CLR_HDCP 0x0015 +#define REG_INT_FLG_CLR_AFE 0x0016 +#define REG_INT_MASK_TOP 0x0017 +#define REG_INT_MASK_SUS 0x0018 +#define REG_INT_MASK_DDC 0x0019 +#define REG_INT_MASK_RATE 0x001A +#define REG_INT_MASK_MODE 0x001B +#define REG_INT_MASK_INFO 0x001C +#define REG_INT_MASK_AUDIO 0x001D +#define REG_INT_MASK_HDCP 0x001E +#define REG_INT_MASK_AFE 0x001F +#define REG_DETECT_5V 0x0020 +#define REG_SUS_STATUS 0x0021 +#define REG_V_PER 0x0022 +#define REG_H_PER 0x0025 +#define REG_HS_WIDTH 0x0027 +#define REG_FMT_H_TOT 0x0029 +#define REG_FMT_H_ACT 0x002b +#define REG_FMT_H_FRONT 0x002d +#define REG_FMT_H_SYNC 0x002f +#define REG_FMT_H_BACK 0x0031 +#define REG_FMT_V_TOT 0x0033 +#define REG_FMT_V_ACT 0x0035 +#define REG_FMT_V_FRONT_F1 0x0037 +#define REG_FMT_V_FRONT_F2 0x0038 +#define REG_FMT_V_SYNC 0x0039 +#define REG_FMT_V_BACK_F1 0x003a +#define REG_FMT_V_BACK_F2 0x003b +#define REG_FMT_DE_ACT 0x003c +#define REG_RATE_CTRL 0x0040 +#define REG_CLK_MIN_RATE 0x0043 +#define REG_CLK_MAX_RATE 0x0046 +#define REG_CLK_A_STATUS 0x0049 +#define REG_CLK_A_RATE 0x004A +#define REG_DRIFT_CLK_A_REG 0x004D +#define REG_CLK_B_STATUS 0x004E +#define REG_CLK_B_RATE 0x004F +#define REG_DRIFT_CLK_B_REG 0x0052 +#define REG_HDCP_CTRL 0x0060 +#define REG_HDCP_KDS 0x0061 +#define REG_HDCP_BCAPS 0x0063 +#define REG_HDCP_KEY_CTRL 0x0064 +#define REG_INFO_CTRL 0x0076 +#define REG_INFO_EXCEED 0x0077 +#define REG_PIX_REPEAT 0x007B +#define REG_AUDIO_PATH 0x007C +#define REG_AUDCFG 0x007D +#define REG_AUDIO_OUT_ENABLE 0x007E +#define REG_AUDIO_OUT_HIZ 0x007F +#define REG_VDP_CTRL 0x0080 +#define REG_VDP_MATRIX 0x0081 +#define REG_VHREF_CTRL 0x00A0 +#define REG_PXCNT_PR 0x00A2 +#define REG_PXCNT_NPIX 0x00A4 +#define REG_LCNT_PR 0x00A6 +#define REG_LCNT_NLIN 0x00A8 +#define REG_HREF_S 0x00AA +#define REG_HREF_E 0x00AC +#define REG_HS_S 0x00AE +#define REG_HS_E 0x00B0 +#define REG_VREF_F1_S 0x00B2 +#define REG_VREF_F1_WIDTH 0x00B4 +#define REG_VREF_F2_S 0x00B5 +#define REG_VREF_F2_WIDTH 0x00B7 +#define REG_VS_F1_LINE_S 0x00B8 +#define REG_VS_F1_LINE_WIDTH 0x00BA +#define REG_VS_F2_LINE_S 0x00BB +#define REG_VS_F2_LINE_WIDTH 0x00BD +#define REG_VS_F1_PIX_S 0x00BE +#define REG_VS_F1_PIX_E 0x00C0 +#define REG_VS_F2_PIX_S 0x00C2 +#define REG_VS_F2_PIX_E 0x00C4 +#define REG_FREF_F1_S 0x00C6 +#define REG_FREF_F2_S 0x00C8 +#define REG_FDW_S 0x00ca +#define REG_FDW_E 0x00cc +#define REG_BLK_GY 0x00da +#define REG_BLK_BU 0x00dc +#define REG_BLK_RV 0x00de +#define REG_FILTERS_CTRL 0x00e0 +#define REG_DITHERING_CTRL 0x00E9 +#define REG_OF 0x00EA +#define REG_PCLK 0x00EB +#define REG_HS_HREF 0x00EC +#define REG_VS_VREF 0x00ED +#define REG_DE_FREF 0x00EE +#define REG_VP35_32_CTRL 0x00EF +#define REG_VP31_28_CTRL 0x00F0 +#define REG_VP27_24_CTRL 0x00F1 +#define REG_VP23_20_CTRL 0x00F2 +#define REG_VP19_16_CTRL 0x00F3 +#define REG_VP15_12_CTRL 0x00F4 +#define REG_VP11_08_CTRL 0x00F5 +#define REG_VP07_04_CTRL 0x00F6 +#define REG_VP03_00_CTRL 0x00F7 +#define REG_CURPAGE_00H 0xFF + +#define MASK_VPER 0x3fffff +#define MASK_VHREF 0x3fff +#define MASK_HPER 0x0fff +#define MASK_HSWIDTH 0x03ff + +/* Input Select */ +#define INPUT_SEL_RST_FMT BIT(7) /* 1=reset format measurement */ +#define INPUT_SEL_RST_VDP BIT(2) /* 1=reset video data path */ +#define INPUT_SEL_OUT_MODE BIT(1) /* 0=loop 1=bypass */ +#define INPUT_SEL_B BIT(0) /* 0=inputA 1=inputB */ + +/* Service Mode */ +#define SVC_MODE_CLK2_MASK 0xc0 +#define SVC_MODE_CLK2_SHIFT 6 +#define SVC_MODE_CLK2_XTL 0L +#define SVC_MODE_CLK2_XTLDIV2 1L +#define SVC_MODE_CLK2_HDMIX2 3L +#define SVC_MODE_CLK1_MASK 0x30 +#define SVC_MODE_CLK1_SHIFT 4 +#define SVC_MODE_CLK1_XTAL 0L +#define SVC_MODE_CLK1_XTLDIV2 1L +#define SVC_MODE_CLK1_HDMI 3L +#define SVC_MODE_RAMP BIT(3) /* 0=colorbar 1=ramp */ +#define SVC_MODE_PAL BIT(2) /* 0=NTSC(480i/p) 1=PAL(576i/p) */ +#define SVC_MODE_INT_PROG BIT(1) /* 0=interlaced 1=progressive */ +#define SVC_MODE_SM_ON BIT(0) /* Enable color bars and tone gen */ + +/* HDP Manual Control */ +#define HPD_MAN_CTRL_HPD_PULSE BIT(7) /* HPD Pulse low 110ms */ +#define HPD_MAN_CTRL_5VEN BIT(2) /* Output 5V */ +#define HPD_MAN_CTRL_HPD_B BIT(1) /* Assert HPD High for Input A */ +#define HPD_MAN_CTRL_HPD_A BIT(0) /* Assert HPD High for Input A */ + +/* RT_MAN_CTRL */ +#define RT_MAN_CTRL_RT_AUTO BIT(7) +#define RT_MAN_CTRL_RT BIT(6) +#define RT_MAN_CTRL_RT_B BIT(1) /* enable TMDS pull-up on Input B */ +#define RT_MAN_CTRL_RT_A BIT(0) /* enable TMDS pull-up on Input A */ + +/* VDP_CTRL */ +#define VDP_CTRL_COMPDEL_BP BIT(5) /* bypass compdel */ +#define VDP_CTRL_FORMATTER_BP BIT(4) /* bypass formatter */ +#define VDP_CTRL_PREFILTER_BP BIT(1) /* bypass prefilter */ +#define VDP_CTRL_MATRIX_BP BIT(0) /* bypass matrix conversion */ + +/* REG_VHREF_CTRL */ +#define VHREF_INT_DET BIT(7) /* interlace detect: 1=alt 0=frame */ +#define VHREF_VSYNC_MASK 0x60 +#define VHREF_VSYNC_SHIFT 6 +#define VHREF_VSYNC_AUTO 0L +#define VHREF_VSYNC_FDW 1L +#define VHREF_VSYNC_EVEN 2L +#define VHREF_VSYNC_ODD 3L +#define VHREF_STD_DET_MASK 0x18 +#define VHREF_STD_DET_SHIFT 3 +#define VHREF_STD_DET_PAL 0L +#define VHREF_STD_DET_NTSC 1L +#define VHREF_STD_DET_AUTO 2L +#define VHREF_STD_DET_OFF 3L +#define VHREF_VREF_SRC_STD BIT(2) /* 1=from standard 0=manual */ +#define VHREF_HREF_SRC_STD BIT(1) /* 1=from standard 0=manual */ +#define VHREF_HSYNC_SEL_HS BIT(0) /* 1=HS 0=VS */ + +/* AUDIO_OUT_ENABLE */ +#define AUDIO_OUT_ENABLE_ACLK BIT(5) +#define AUDIO_OUT_ENABLE_WS BIT(4) +#define AUDIO_OUT_ENABLE_AP3 BIT(3) +#define AUDIO_OUT_ENABLE_AP2 BIT(2) +#define AUDIO_OUT_ENABLE_AP1 BIT(1) +#define AUDIO_OUT_ENABLE_AP0 BIT(0) + +/* Prefilter Control */ +#define FILTERS_CTRL_BU_MASK 0x0c +#define FILTERS_CTRL_BU_SHIFT 2 +#define FILTERS_CTRL_RV_MASK 0x03 +#define FILTERS_CTRL_RV_SHIFT 0 +#define FILTERS_CTRL_OFF 0L /* off */ +#define FILTERS_CTRL_2TAP 1L /* 2 Taps */ +#define FILTERS_CTRL_7TAP 2L /* 7 Taps */ +#define FILTERS_CTRL_2_7TAP 3L /* 2/7 Taps */ + +/* PCLK Configuration */ +#define PCLK_DELAY_MASK 0x70 +#define PCLK_DELAY_SHIFT 4 /* Pixel delay (-8..+7) */ +#define PCLK_INV_SHIFT 2 +#define PCLK_SEL_MASK 0x03 /* clock scaler */ +#define PCLK_SEL_SHIFT 0 +#define PCLK_SEL_X1 0L +#define PCLK_SEL_X2 1L +#define PCLK_SEL_DIV2 2L +#define PCLK_SEL_DIV4 3L + +/* Pixel Repeater */ +#define PIX_REPEAT_MASK_UP_SEL 0x30 +#define PIX_REPEAT_MASK_REP 0x0f +#define PIX_REPEAT_SHIFT 4 +#define PIX_REPEAT_CHROMA 1 + +/* Page 0x01 - HDMI info and packets */ +#define REG_HDMI_FLAGS 0x0100 +#define REG_DEEP_COLOR_MODE 0x0101 +#define REG_AUDIO_FLAGS 0x0108 +#define REG_AUDIO_FREQ 0x0109 +#define REG_ACP_PACKET_TYPE 0x0141 +#define REG_ISRC1_PACKET_TYPE 0x0161 +#define REG_ISRC2_PACKET_TYPE 0x0181 +#define REG_GBD_PACKET_TYPE 0x01a1 + +/* HDMI_FLAGS */ +#define HDMI_FLAGS_AUDIO BIT(7) /* Audio packet in last videoframe */ +#define HDMI_FLAGS_HDMI BIT(6) /* HDMI detected */ +#define HDMI_FLAGS_EESS BIT(5) /* EESS detected */ +#define HDMI_FLAGS_HDCP BIT(4) /* HDCP detected */ +#define HDMI_FLAGS_AVMUTE BIT(3) /* AVMUTE */ +#define HDMI_FLAGS_AUD_LAYOUT BIT(2) /* Layout status Audio sample packet */ +#define HDMI_FLAGS_AUD_FIFO_OF BIT(1) /* FIFO read/write pointers crossed */ +#define HDMI_FLAGS_AUD_FIFO_LOW BIT(0) /* FIFO read ptr within 2 of write */ + +/* Page 0x12 - HDMI Extra control and debug */ +#define REG_CLK_CFG 0x1200 +#define REG_CLK_OUT_CFG 0x1201 +#define REG_CFG1 0x1202 +#define REG_CFG2 0x1203 +#define REG_WDL_CFG 0x1210 +#define REG_DELOCK_DELAY 0x1212 +#define REG_PON_OVR_EN 0x12A0 +#define REG_PON_CBIAS 0x12A1 +#define REG_PON_RESCAL 0x12A2 +#define REG_PON_RES 0x12A3 +#define REG_PON_CLK 0x12A4 +#define REG_PON_PLL 0x12A5 +#define REG_PON_EQ 0x12A6 +#define REG_PON_DES 0x12A7 +#define REG_PON_OUT 0x12A8 +#define REG_PON_MUX 0x12A9 +#define REG_MODE_REC_CFG1 0x12F8 +#define REG_MODE_REC_CFG2 0x12F9 +#define REG_MODE_REC_STS 0x12FA +#define REG_AUDIO_LAYOUT 0x12D0 + +#define PON_EN 1 +#define PON_DIS 0 + +/* CLK CFG */ +#define CLK_CFG_INV_OUT_CLK BIT(7) +#define CLK_CFG_INV_BUS_CLK BIT(6) +#define CLK_CFG_SEL_ACLK_EN BIT(1) +#define CLK_CFG_SEL_ACLK BIT(0) +#define CLK_CFG_DIS 0 + +/* Page 0x13 - HDMI Extra control and debug */ +#define REG_DEEP_COLOR_CTRL 0x1300 +#define REG_CGU_DBG_SEL 0x1305 +#define REG_HDCP_DDC_ADDR 0x1310 +#define REG_HDCP_KIDX 0x1316 +#define REG_DEEP_PLL7_BYP 0x1347 +#define REG_HDCP_DE_CTRL 0x1370 +#define REG_HDCP_EP_FILT_CTRL 0x1371 +#define REG_HDMI_CTRL 0x1377 +#define REG_HMTP_CTRL 0x137a +#define REG_TIMER_D 0x13CF +#define REG_SUS_SET_RGB0 0x13E1 +#define REG_SUS_SET_RGB1 0x13E2 +#define REG_SUS_SET_RGB2 0x13E3 +#define REG_SUS_SET_RGB3 0x13E4 +#define REG_SUS_SET_RGB4 0x13E5 +#define REG_MAN_SUS_HDMI_SEL 0x13E8 +#define REG_MAN_HDMI_SET 0x13E9 +#define REG_SUS_CLOCK_GOOD 0x13EF + +/* HDCP DE Control */ +#define HDCP_DE_MODE_MASK 0xc0 /* DE Measurement mode */ +#define HDCP_DE_MODE_SHIFT 6 +#define HDCP_DE_REGEN_EN BIT(5) /* enable regen mode */ +#define HDCP_DE_FILTER_MASK 0x18 /* DE filter sensitivity */ +#define HDCP_DE_FILTER_SHIFT 3 +#define HDCP_DE_COMP_MASK 0x07 /* DE Composition mode */ +#define HDCP_DE_COMP_MIXED 6L +#define HDCP_DE_COMP_OR 5L +#define HDCP_DE_COMP_AND 4L +#define HDCP_DE_COMP_CH3 3L +#define HDCP_DE_COMP_CH2 2L +#define HDCP_DE_COMP_CH1 1L +#define HDCP_DE_COMP_CH0 0L + +/* HDCP EP Filter Control */ +#define HDCP_EP_FIL_CTL_MASK 0x30 +#define HDCP_EP_FIL_CTL_SHIFT 4 +#define HDCP_EP_FIL_VS_MASK 0x0c +#define HDCP_EP_FIL_VS_SHIFT 2 +#define HDCP_EP_FIL_HS_MASK 0x03 +#define HDCP_EP_FIL_HS_SHIFT 0 + +/* HDMI_CTRL */ +#define HDMI_CTRL_MUTE_MASK 0x0c +#define HDMI_CTRL_MUTE_SHIFT 2 +#define HDMI_CTRL_MUTE_AUTO 0L +#define HDMI_CTRL_MUTE_OFF 1L +#define HDMI_CTRL_MUTE_ON 2L +#define HDMI_CTRL_HDCP_MASK 0x03 +#define HDMI_CTRL_HDCP_SHIFT 0 +#define HDMI_CTRL_HDCP_EESS 2L +#define HDMI_CTRL_HDCP_OESS 1L +#define HDMI_CTRL_HDCP_AUTO 0L + +/* CGU_DBG_SEL bits */ +#define CGU_DBG_CLK_SEL_MASK 0x18 +#define CGU_DBG_CLK_SEL_SHIFT 3 +#define CGU_DBG_XO_FRO_SEL BIT(2) +#define CGU_DBG_VDP_CLK_SEL BIT(1) +#define CGU_DBG_PIX_CLK_SEL BIT(0) + +/* REG_MAN_SUS_HDMI_SEL / REG_MAN_HDMI_SET bits */ +#define MAN_DIS_OUT_BUF BIT(7) +#define MAN_DIS_ANA_PATH BIT(6) +#define MAN_DIS_HDCP BIT(5) +#define MAN_DIS_TMDS_ENC BIT(4) +#define MAN_DIS_TMDS_FLOW BIT(3) +#define MAN_RST_HDCP BIT(2) +#define MAN_RST_TMDS_ENC BIT(1) +#define MAN_RST_TMDS_FLOW BIT(0) + +/* Page 0x14 - Audio Extra control and debug */ +#define REG_FIFO_LATENCY_VAL 0x1403 +#define REG_AUDIO_CLOCK 0x1411 +#define REG_TEST_NCTS_CTRL 0x1415 +#define REG_TEST_AUDIO_FREQ 0x1426 +#define REG_TEST_MODE 0x1437 + +/* Audio Clock Configuration */ +#define AUDIO_CLOCK_PLL_PD BIT(7) /* powerdown PLL */ +#define AUDIO_CLOCK_SEL_MASK 0x7f +#define AUDIO_CLOCK_SEL_16FS 0L /* 16*fs */ +#define AUDIO_CLOCK_SEL_32FS 1L /* 32*fs */ +#define AUDIO_CLOCK_SEL_64FS 2L /* 64*fs */ +#define AUDIO_CLOCK_SEL_128FS 3L /* 128*fs */ +#define AUDIO_CLOCK_SEL_256FS 4L /* 256*fs */ +#define AUDIO_CLOCK_SEL_512FS 5L /* 512*fs */ + +/* Page 0x20: EDID and Hotplug Detect */ +#define REG_EDID_IN_BYTE0 0x2000 /* EDID base */ +#define REG_EDID_IN_VERSION 0x2080 +#define REG_EDID_ENABLE 0x2081 +#define REG_HPD_POWER 0x2084 +#define REG_HPD_AUTO_CTRL 0x2085 +#define REG_HPD_DURATION 0x2086 +#define REG_RX_HPD_HEAC 0x2087 + +/* EDID_ENABLE */ +#define EDID_ENABLE_NACK_OFF BIT(7) +#define EDID_ENABLE_EDID_ONLY BIT(6) +#define EDID_ENABLE_B_EN BIT(1) +#define EDID_ENABLE_A_EN BIT(0) + +/* HPD Power */ +#define HPD_POWER_BP_MASK 0x0c +#define HPD_POWER_BP_SHIFT 2 +#define HPD_POWER_BP_LOW 0L +#define HPD_POWER_BP_HIGH 1L +#define HPD_POWER_EDID_ONLY BIT(1) + +/* HPD Auto control */ +#define HPD_AUTO_READ_EDID BIT(7) +#define HPD_AUTO_HPD_F3TECH BIT(5) +#define HPD_AUTO_HP_OTHER BIT(4) +#define HPD_AUTO_HPD_UNSEL BIT(3) +#define HPD_AUTO_HPD_ALL_CH BIT(2) +#define HPD_AUTO_HPD_PRV_CH BIT(1) +#define HPD_AUTO_HPD_NEW_CH BIT(0) + +/* Page 0x21 - EDID content */ +#define REG_EDID_IN_BYTE128 0x2100 /* CEA Extension block */ +#define REG_EDID_IN_SPA_SUB 0x2180 +#define REG_EDID_IN_SPA_AB_A 0x2181 +#define REG_EDID_IN_SPA_CD_A 0x2182 +#define REG_EDID_IN_CKSUM_A 0x2183 +#define REG_EDID_IN_SPA_AB_B 0x2184 +#define REG_EDID_IN_SPA_CD_B 0x2185 +#define REG_EDID_IN_CKSUM_B 0x2186 + +/* Page 0x30 - NV Configuration */ +#define REG_RT_AUTO_CTRL 0x3000 +#define REG_EQ_MAN_CTRL0 0x3001 +#define REG_EQ_MAN_CTRL1 0x3002 +#define REG_OUTPUT_CFG 0x3003 +#define REG_MUTE_CTRL 0x3004 +#define REG_SLAVE_ADDR 0x3005 +#define REG_CMTP_REG6 0x3006 +#define REG_CMTP_REG7 0x3007 +#define REG_CMTP_REG8 0x3008 +#define REG_CMTP_REG9 0x3009 +#define REG_CMTP_REGA 0x300A +#define REG_CMTP_REGB 0x300B +#define REG_CMTP_REGC 0x300C +#define REG_CMTP_REGD 0x300D +#define REG_CMTP_REGE 0x300E +#define REG_CMTP_REGF 0x300F +#define REG_CMTP_REG10 0x3010 +#define REG_CMTP_REG11 0x3011 + +/* Page 0x80 - CEC */ +#define REG_PWR_CONTROL 0x80F4 +#define REG_OSC_DIVIDER 0x80F5 +#define REG_EN_OSC_PERIOD_LSB 0x80F8 +#define REG_CONTROL 0x80FF + +/* global interrupt flags (INT_FLG_CRL_TOP) */ +#define INTERRUPT_AFE BIT(7) /* AFE module */ +#define INTERRUPT_HDCP BIT(6) /* HDCP module */ +#define INTERRUPT_AUDIO BIT(5) /* Audio module */ +#define INTERRUPT_INFO BIT(4) /* Infoframe module */ +#define INTERRUPT_MODE BIT(3) /* HDMI mode module */ +#define INTERRUPT_RATE BIT(2) /* rate module */ +#define INTERRUPT_DDC BIT(1) /* DDC module */ +#define INTERRUPT_SUS BIT(0) /* SUS module */ + +/* INT_FLG_CLR_HDCP bits */ +#define MASK_HDCP_MTP BIT(7) /* HDCP MTP busy */ +#define MASK_HDCP_DLMTP BIT(4) /* HDCP end download MTP to SRAM */ +#define MASK_HDCP_DLRAM BIT(3) /* HDCP end download keys from SRAM */ +#define MASK_HDCP_ENC BIT(2) /* HDCP ENC */ +#define MASK_STATE_C5 BIT(1) /* HDCP State C5 reached */ +#define MASK_AKSV BIT(0) /* AKSV received (start of auth) */ + +/* INT_FLG_CLR_RATE bits */ +#define MASK_RATE_B_DRIFT BIT(7) /* Rate measurement drifted */ +#define MASK_RATE_B_ST BIT(6) /* Rate measurement stability change */ +#define MASK_RATE_B_ACT BIT(5) /* Rate measurement activity change */ +#define MASK_RATE_B_PST BIT(4) /* Rate measreument presence change */ +#define MASK_RATE_A_DRIFT BIT(3) /* Rate measurement drifted */ +#define MASK_RATE_A_ST BIT(2) /* Rate measurement stability change */ +#define MASK_RATE_A_ACT BIT(1) /* Rate measurement presence change */ +#define MASK_RATE_A_PST BIT(0) /* Rate measreument presence change */ + +/* INT_FLG_CLR_SUS (Start Up Sequencer) bits */ +#define MASK_MPT BIT(7) /* Config MTP end of process */ +#define MASK_FMT BIT(5) /* Video format changed */ +#define MASK_RT_PULSE BIT(4) /* End of termination resistance pulse */ +#define MASK_SUS_END BIT(3) /* SUS last state reached */ +#define MASK_SUS_ACT BIT(2) /* Activity of selected input changed */ +#define MASK_SUS_CH BIT(1) /* Selected input changed */ +#define MASK_SUS_ST BIT(0) /* SUS state changed */ + +/* INT_FLG_CLR_DDC bits */ +#define MASK_EDID_MTP BIT(7) /* EDID MTP end of process */ +#define MASK_DDC_ERR BIT(6) /* master DDC error */ +#define MASK_DDC_CMD_DONE BIT(5) /* master DDC cmd send correct */ +#define MASK_READ_DONE BIT(4) /* End of down EDID read */ +#define MASK_RX_DDC_SW BIT(3) /* Output DDC switching finished */ +#define MASK_HDCP_DDC_SW BIT(2) /* HDCP DDC switching finished */ +#define MASK_HDP_PULSE_END BIT(1) /* End of Hot Plug Detect pulse */ +#define MASK_DET_5V BIT(0) /* Detection of +5V */ + +/* INT_FLG_CLR_MODE bits */ +#define MASK_HDMI_FLG BIT(7) /* HDMI mode/avmute/encrypt/FIFO fail */ +#define MASK_GAMUT BIT(6) /* Gamut packet */ +#define MASK_ISRC2 BIT(5) /* ISRC2 packet */ +#define MASK_ISRC1 BIT(4) /* ISRC1 packet */ +#define MASK_ACP BIT(3) /* Audio Content Protection packet */ +#define MASK_DC_NO_GCP BIT(2) /* GCP not received in 5 frames */ +#define MASK_DC_PHASE BIT(1) /* deepcolor pixel phase needs update */ +#define MASK_DC_MODE BIT(0) /* deepcolor color depth changed */ + +/* INT_FLG_CLR_INFO bits (Infoframe Change Status) */ +#define MASK_MPS_IF BIT(6) /* MPEG Source Product */ +#define MASK_AUD_IF BIT(5) /* Audio */ +#define MASK_SPD_IF BIT(4) /* Source Product Descriptor */ +#define MASK_AVI_IF BIT(3) /* Auxiliary Video IF */ +#define MASK_VS_IF_OTHER_BK2 BIT(2) /* Vendor Specific (bank2) */ +#define MASK_VS_IF_OTHER_BK1 BIT(1) /* Vendor Specific (bank1) */ +#define MASK_VS_IF_HDMI BIT(0) /* Vendor Specific (w/ HDMI LLC code) */ + +/* INT_FLG_CLR_AUDIO bits */ +#define MASK_AUDIO_FREQ_FLG BIT(5) /* Audio freq change */ +#define MASK_AUDIO_FLG BIT(4) /* DST, OBA, HBR, ASP change */ +#define MASK_MUTE_FLG BIT(3) /* Audio Mute */ +#define MASK_CH_STATE BIT(2) /* Channel status */ +#define MASK_UNMUTE_FIFO BIT(1) /* Audio Unmute */ +#define MASK_ERROR_FIFO_PT BIT(0) /* Audio FIFO pointer error */ + +/* INT_FLG_CLR_AFE bits */ +#define MASK_AFE_WDL_UNLOCKED BIT(7) /* Wordlocker was unlocked */ +#define MASK_AFE_GAIN_DONE BIT(6) /* Gain calibration done */ +#define MASK_AFE_OFFSET_DONE BIT(5) /* Offset calibration done */ +#define MASK_AFE_ACTIVITY_DET BIT(4) /* Activity detected on data */ +#define MASK_AFE_PLL_LOCK BIT(3) /* TMDS PLL is locked */ +#define MASK_AFE_TRMCAL_DONE BIT(2) /* Termination calibration done */ +#define MASK_AFE_ASU_STATE BIT(1) /* ASU state is reached */ +#define MASK_AFE_ASU_READY BIT(0) /* AFE calibration done: TMDS ready */ + +/* Audio Output */ +#define AUDCFG_CLK_INVERT BIT(7) /* invert A_CLK polarity */ +#define AUDCFG_TEST_TONE BIT(6) /* enable test tone generator */ +#define AUDCFG_BUS_SHIFT 5 +#define AUDCFG_BUS_I2S 0L +#define AUDCFG_BUS_SPDIF 1L +#define AUDCFG_I2SW_SHIFT 4 +#define AUDCFG_I2SW_16 0L +#define AUDCFG_I2SW_32 1L +#define AUDCFG_AUTO_MUTE_EN BIT(3) /* Enable Automatic audio mute */ +#define AUDCFG_HBR_SHIFT 2 +#define AUDCFG_HBR_STRAIGHT 0L /* straight via AP0 */ +#define AUDCFG_HBR_DEMUX 1L /* demuxed via AP0:AP3 */ +#define AUDCFG_TYPE_MASK 0x03 +#define AUDCFG_TYPE_SHIFT 0 +#define AUDCFG_TYPE_DST 3L /* Direct Stream Transfer (DST) */ +#define AUDCFG_TYPE_OBA 2L /* One Bit Audio (OBA) */ +#define AUDCFG_TYPE_HBR 1L /* High Bit Rate (HBR) */ +#define AUDCFG_TYPE_PCM 0L /* Audio samples */ + +/* Video Formatter */ +#define OF_VP_ENABLE BIT(7) /* VP[35:0]/HS/VS/DE/CLK */ +#define OF_BLK BIT(4) /* blanking codes */ +#define OF_TRC BIT(3) /* timing codes (SAV/EAV) */ +#define OF_FMT_MASK 0x3 +#define OF_FMT_444 0L /* RGB444/YUV444 */ +#define OF_FMT_422_SMPT 1L /* YUV422 semi-planar */ +#define OF_FMT_422_CCIR 2L /* YUV422 CCIR656 */ + +/* HS/HREF output control */ +#define HS_HREF_DELAY_MASK 0xf0 +#define HS_HREF_DELAY_SHIFT 4 /* Pixel delay (-8..+7) */ +#define HS_HREF_PXQ_SHIFT 3 /* Timing codes from HREF */ +#define HS_HREF_INV_SHIFT 2 /* polarity (1=invert) */ +#define HS_HREF_SEL_MASK 0x03 +#define HS_HREF_SEL_SHIFT 0 +#define HS_HREF_SEL_HS_VHREF 0L /* HS from VHREF */ +#define HS_HREF_SEL_HREF_VHREF 1L /* HREF from VHREF */ +#define HS_HREF_SEL_HREF_HDMI 2L /* HREF from HDMI */ +#define HS_HREF_SEL_NONE 3L /* not generated */ + +/* VS output control */ +#define VS_VREF_DELAY_MASK 0xf0 +#define VS_VREF_DELAY_SHIFT 4 /* Pixel delay (-8..+7) */ +#define VS_VREF_INV_SHIFT 2 /* polarity (1=invert) */ +#define VS_VREF_SEL_MASK 0x03 +#define VS_VREF_SEL_SHIFT 0 +#define VS_VREF_SEL_VS_VHREF 0L /* VS from VHREF */ +#define VS_VREF_SEL_VREF_VHREF 1L /* VREF from VHREF */ +#define VS_VREF_SEL_VREF_HDMI 2L /* VREF from HDMI */ +#define VS_VREF_SEL_NONE 3L /* not generated */ + +/* DE/FREF output control */ +#define DE_FREF_DELAY_MASK 0xf0 +#define DE_FREF_DELAY_SHIFT 4 /* Pixel delay (-8..+7) */ +#define DE_FREF_DE_PXQ_SHIFT 3 /* Timing codes from DE */ +#define DE_FREF_INV_SHIFT 2 /* polarity (1=invert) */ +#define DE_FREF_SEL_MASK 0x03 +#define DE_FREF_SEL_SHIFT 0 +#define DE_FREF_SEL_DE_VHREF 0L /* DE from VHREF (HREF and not(VREF) */ +#define DE_FREF_SEL_FREF_VHREF 1L /* FREF from VHREF */ +#define DE_FREF_SEL_FREF_HDMI 2L /* FREF from HDMI */ +#define DE_FREF_SEL_NONE 3L /* not generated */ + +/* HDMI_SOFT_RST bits */ +#define RESET_DC BIT(7) /* Reset deep color module */ +#define RESET_HDCP BIT(6) /* Reset HDCP module */ +#define RESET_KSV BIT(5) /* Reset KSV-FIFO */ +#define RESET_SCFG BIT(4) /* Reset HDCP and repeater function */ +#define RESET_HCFG BIT(3) /* Reset HDCP DDC part */ +#define RESET_PA BIT(2) /* Reset polarity adjust */ +#define RESET_EP BIT(1) /* Reset Error protection */ +#define RESET_TMDS BIT(0) /* Reset TMDS (calib, encoding, flow) */ + +/* HDMI_INFO_RST bits */ +#define NACK_HDCP BIT(7) /* No ACK on HDCP request */ +#define RESET_FIFO BIT(4) /* Reset Audio FIFO control */ +#define RESET_GAMUT BIT(3) /* Clear Gamut packet */ +#define RESET_AI BIT(2) /* Clear ACP and ISRC packets */ +#define RESET_IF BIT(1) /* Clear all Audio infoframe packets */ +#define RESET_AUDIO BIT(0) /* Reset Audio FIFO control */ + +/* HDCP_BCAPS bits */ +#define HDCP_HDMI BIT(7) /* HDCP suports HDMI (vs DVI only) */ +#define HDCP_REPEATER BIT(6) /* HDCP supports repeater function */ +#define HDCP_READY BIT(5) /* set by repeater function */ +#define HDCP_FAST BIT(4) /* Up to 400kHz */ +#define HDCP_11 BIT(1) /* HDCP 1.1 supported */ +#define HDCP_FAST_REAUTH BIT(0) /* fast reauthentication supported */ + +/* Audio output formatter */ +#define AUDIO_LAYOUT_SP_FLAG BIT(2) /* sp flag used by FIFO */ +#define AUDIO_LAYOUT_MANUAL BIT(1) /* manual layout (vs per pkt) */ +#define AUDIO_LAYOUT_LAYOUT1 BIT(0) /* Layout1: AP0-3 vs Layout0:AP0 */ + +/* masks for interrupt status registers */ +#define MASK_SUS_STATUS 0x1F +#define LAST_STATE_REACHED 0x1B +#define MASK_CLK_STABLE 0x04 +#define MASK_CLK_ACTIVE 0x02 +#define MASK_SUS_STATE 0x10 +#define MASK_SR_FIFO_FIFO_CTRL 0x30 +#define MASK_AUDIO_FLAG 0x10 + +/* Rate measurement */ +#define RATE_REFTIM_ENABLE 0x01 +#define CLK_MIN_RATE 0x0057e4 +#define CLK_MAX_RATE 0x0395f8 +#define WDL_CFG_VAL 0x82 +#define DC_FILTER_VAL 0x31 + +/* Infoframe */ +#define VS_HDMI_IF_UPDATE 0x0200 +#define VS_HDMI_IF 0x0201 +#define VS_BK1_IF_UPDATE 0x0220 +#define VS_BK1_IF 0x0221 +#define VS_BK2_IF_UPDATE 0x0240 +#define VS_BK2_IF 0x0241 +#define AVI_IF_UPDATE 0x0260 +#define AVI_IF 0x0261 +#define SPD_IF_UPDATE 0x0280 +#define SPD_IF 0x0281 +#define AUD_IF_UPDATE 0x02a0 +#define AUD_IF 0x02a1 +#define MPS_IF_UPDATE 0x02c0 +#define MPS_IF 0x02c1 + +/* 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 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", +}; + +/* + * Video Output formats + * There are 24 video output pins on TDA19971 and 36 on TDA19973 supporting + * the following output formats: + * - RGB444 + * - YUV444 + * - YUV422 semi-planar based on ITU-R BT.601 + * - YUV422 ITU-R BT.656 + * + * TDA19971 can output 3x8bits per pixel + * TDA19973 can output 3x8, 3x10, or 3x12bit per pixel + * + * Deep color modes (3x10 or 3x12 bits) are possible in any case. + * + * Reference: NXP AN1206 - TDA19971_TDA19973 receiver HW recommendation: 3.3.4 + */ +static u32 tda19971_video_formats[] = { + /* 24bit RGB444: 1 pixel in 1x24bit sample: VP[23:0] */ + MEDIA_BUS_FMT_RGB888_1X24, + /* 24bit YUV444: 1 pixel in 1x24bit sample: VP[23:0] */ + MEDIA_BUS_FMT_YUV8_1X24, + /* 24bit YUV422: 1 pixel in 1x24bit sample: VP[23:12]/VP[11:0] */ + MEDIA_BUS_FMT_UYVY12_1X24, + /* 20bit YUV422: 1 pixel in 1x20bit sample: VP[23:14]/VP[11:2] */ + MEDIA_BUS_FMT_UYVY10_1X20, + /* 16bit YUV422: 1 pixel in 1x16bit sample: VP[23:16]/VP[15:8] */ + MEDIA_BUS_FMT_UYVY8_1X16, + /* 12bit CCIR656: 1 pixel in 2x12bit samples: VP[23:12] */ + MEDIA_BUS_FMT_UYVY12_2X12, + /* 10bit CCIR656: 1 pixel in 2x10bit samples: VP[23:14] */ + MEDIA_BUS_FMT_UYVY10_2X10, + /* 8bit CCIR656: 1 pixel in 2x8bit samples: VP[23:16] */ + MEDIA_BUS_FMT_UYVY8_2X8, +}; +static u32 tda19973_video_formats[] = { + /* 36bit RGB444: 1 pixel in 1x36bit sample on VP[35:0] */ + MEDIA_BUS_FMT_RGB121212_1X36, + /* 36bit YUV444 1 pixel in 1x36bit sample on VP[35:0] */ + MEDIA_BUS_FMT_YUV12_1X36, + /* 24bit YUV422: 1 pixel in 1x24bit sample on VP[35:24]/VP[11:0] */ + MEDIA_BUS_FMT_UYVY12_1X24, + /* 12bit CCIR656: 1 pixel in 2x12bit samples on VP[11:0] */ + MEDIA_BUS_FMT_UYVY12_2X12, +}; + +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 */ + 480, 1080, /* min/max height */ + 13000000, 165000000, /* min/max pixelclock */ + V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT, /* standards */ + V4L2_DV_BT_CAP_PROGRESSIVE /* capabilities */ + ) +}; + +/* + * Video Input formats + */ +struct vhref_values { + u16 href_start; + u16 href_end; + u16 vref_f1_start; + u8 vref_f1_width; + u16 vref_f2_start; + u8 vref_f2_width; + u16 fieldref_f1_start; + u8 fieldPolarity; + u16 fieldref_f2_start; +}; + +struct tda1997x_video_std { + const struct v4l2_dv_timings timings; + const struct vhref_values vhref_values; +}; + +static const struct tda1997x_video_std tda1997x_hdmi_modes[] = { + /* Low TV */ + { V4L2_DV_BT_CEA_1280X720P24, + {261, 1541, 745, 30, 0, 0, 1, 0, 0} + }, + { V4L2_DV_BT_CEA_1280X720P25, + {261, 1541, 745, 30, 0, 0, 1, 0, 0} + }, + { V4L2_DV_BT_CEA_1280X720P30, + {261, 1541, 745, 30, 0, 0, 1, 0, 0} + }, + { V4L2_DV_BT_CEA_1920X1080P24, + {193, 2113, 1121, 45, 0, 0, 1, 0, 0} + }, + { V4L2_DV_BT_CEA_1920X1080P25, + {193, 2113, 1121, 45, 0, 0, 1, 0, 0} + }, + { V4L2_DV_BT_CEA_1920X1080P30, + {193, 2113, 1121, 45, 0, 0, 1, 0, 0} + }, + + /* 60 Hz TV */ + { V4L2_DV_BT_CEA_720X480P59_94, + {123, 843, 516, 45, 0, 0, 1, 0, 0} + }, + { V4L2_DV_BT_CEA_1280X720P60, + {261, 1541, 745, 30, 0, 0, 1, 0, 0} + }, + { V4L2_DV_BT_CEA_1920X1080I60, + {193, 2113, 1123, 22, 560, 23, 1, 0, 563} + }, + { V4L2_DV_BT_CEA_720X480I59_94, + {120, 840, 521, 22, 258, 23, 1, 0, 263} + }, + { V4L2_DV_BT_CEA_1920X1080P60, + {193, 2113, 1121, 45, 0, 0, 1, 0, 0} + }, + + /* 50 Hz TV */ + { V4L2_DV_BT_CEA_720X576P50, + {133, 853, 620, 49, 0, 0, 1, 0, 0} + }, + { V4L2_DV_BT_CEA_1280X720P50, + {261, 1541, 745, 30, 0, 0, 1, 0, 0} + }, + { V4L2_DV_BT_CEA_1920X1080I50, + {193, 2113, 1123, 22, 560, 23, 1, 0, 563} + }, + { V4L2_DV_BT_CEA_720X576I50, + {133, 853, 623, 24, 310, 25, 1, 0, 313 } + }, + { V4L2_DV_BT_CEA_1920X1080P50, + {193, 2113, 1121, 45, 0, 0, 1, 0, 0} + }, + + /* 60 Hz PC */ + { V4L2_DV_BT_DMT_640X480P60, + {145, 785, 515, 45, 0, 0, 1, 0, 0} + }, + { V4L2_DV_BT_DMT_800X600P60, + {217, 1017, 627, 28, 0, 0, 0, 0, 0} + }, + { V4L2_DV_BT_DMT_1024X768P60, + {297, 1321, 803, 38, 0, 0, 0, 0, 0} + }, + { V4L2_DV_BT_DMT_1280X768P60, + {321, 1601, 795, 30, 0, 0, 0, 0, 0} + }, + { V4L2_DV_BT_DMT_1280X960P60, + {425, 1705, 999, 40, 0, 0, 0, 0, 0} + }, + { V4L2_DV_BT_DMT_1280X1024P60, + {361, 1641, 1065, 42, 0, 0, 0, 0, 0} + }, + { V4L2_DV_BT_DMT_1440X900P60, + {385, 1825, 931, 34, 0, 0, 0, 0, 0} + }, + { V4L2_DV_BT_DMT_1600X1200P60, + {497, 2097, 1249, 50, 0, 0, 0, 0, 0} + }, + { V4L2_DV_BT_DMT_1680X1050P60_RB, + {113, 1793, 1077, 30, 0, 0, 0, 0, 0} + }, + { V4L2_DV_BT_DMT_1920X1200P60_RB, + {113, 2033, 1232, 35, 0, 0, 0, 0, 0} + }, + + /* 75 HZ PC */ + { V4L2_DV_BT_DMT_640X480P75, + {185, 825, 499, 20, 0, 0, 1, 0, 0} + }, + { V4L2_DV_BT_DMT_800X600P75, + {241, 1041, 624, 25, 0, 0, 0, 0, 0} + }, + { V4L2_DV_BT_DMT_1024X768P75, + {273, 1297, 799, 32, 0, 0, 0, 0, 0} + }, + { V4L2_DV_BT_DMT_1280X768P75, + {337, 1617, 802, 37, 0, 0, 0, 0, 0} + }, + { V4L2_DV_BT_DMT_1280X1024P75, + {393, 1673, 1065, 42, 0, 0, 0, 0, 0} + }, + + /* 85 HZ PC */ + { V4L2_DV_BT_DMT_640X480P85, + {137, 777, 508, 29, 0, 0, 1, 0, 0} + }, + { V4L2_DV_BT_DMT_800X600P85, + {217, 1017, 630, 31, 0, 0, 0, 0, 0} + }, + { V4L2_DV_BT_DMT_1024X768P85, + {305, 1329, 807, 40, 0, 0, 0, 0, 0} + }, + { V4L2_DV_BT_DMT_1280X768P85, + {353, 1633, 905, 140, 0, 0, 0, 0, 0} + }, + { V4L2_DV_BT_DMT_1280X1024P85, + {385, 1665, 1071, 48, 0, 0, 0, 0, 0} + }, + + { V4L2_DV_BT_DMT_1360X768P60, + {369, 1729, 792, 27, 0, 0, 0, 0, 0} + }, + { V4L2_DV_BT_DMT_1400X1050P60, + {377, 1777, 1086, 39, 0, 0, 0, 0, 0} + }, + { V4L2_DV_BT_DMT_1400X1050P60_RB, + {113, 1513, 1077, 30, 0, 0, 0, 0, 0} + }, + { V4L2_DV_BT_DMT_1024X768P70, + {281, 1305, 803, 38, 0, 0, 0, 0, 0} + }, + { V4L2_DV_BT_DMT_640X480P72, + {169, 809, 511, 40, 0, 0, 0, 0, 0} + }, + { V4L2_DV_BT_DMT_800X600P72, + {185, 985, 629, 66, 0, 0, 0, 0, 0} + }, + { V4L2_DV_BT_DMT_640X350P85, + {161, 801, 413, 95, 0, 0, 0, 0, 0} + }, + { V4L2_DV_BT_DMT_640X400P85, + {161, 801, 444, 45, 0, 0, 0, 0, 0} + }, + { V4L2_DV_BT_DMT_720X400P85, + {181, 901, 445, 46, 0, 0, 0, 0, 0} + }, +}; + +/* 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; + const u32 *formats; + unsigned int nformats; +}; + +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 state_c5_reached; + char activity_status; + char input_detect[2]; + char vendor[12]; + char product[18]; + + /* video */ + enum hdmi_colorspace colorspace; + enum hdmi_colorimetry colorimetry; + const struct tda1997x_video_std *std; + struct v4l2_dv_timings timings; + int fps; + u32 code; + enum v4l2_mbus_type bus_type; + char vid_fmt; + + /* 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; +}; + +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", + .formats = tda19971_video_formats, + .nformats = ARRAY_SIZE(tda19971_video_formats), + }, + [TDA19973] = { + .type = TDA19973, + .name = "tda19973", + .formats = tda19973_video_formats, + .nformats = ARRAY_SIZE(tda19973_video_formats), + }, +}; + +static inline struct tda1997x_state *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct tda1997x_state, 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) +{ + u8 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) +{ + u8 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; + u8 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; +} + +/* ----------------------------------------------------------------------------- + * Signal Control + */ + +/* + * The color conversion matrix will convert between the colorimetry of the + * HDMI input to the desired output format RGB|YUV + */ +static int +tda1997x_configure_conv(struct v4l2_subdev *sd, + enum hdmi_colorspace colorspace, + enum hdmi_colorimetry colorimetry) +{ + struct tda1997x_state *state = to_state(sd); + /* Colorspace conversion matrix coefficients and offsets */ + struct color_matrix_coefs { + /* 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; + }; + /* Conversion matrixes */ + enum { + ITU709_RGBLIMITED, + RGBLIMITED_ITU601, + ITU601_RGBLIMITED, + }; + static const struct color_matrix_coefs conv_matrix[] = { + /* ITU709 -> RGBLimited */ + { + -256, -2048, -2048, + 4096, -1875, -750, + 4096, 6307, 0, + 4096, 0, 7431, + 256, 256, 256, + }, + /* RGBLimited -> ITU601 */ + { + -256, -256, -256, + 2404, 1225, 467, + -1754, 2095, -341, + -1388, -707, 2095, + 256, 2048, 2048, + }, + /* YUV601 -> RGBLimited */ + { + -256, -2048, -2048, + 4096, -2860, -1378, + 4096, 5615, 0, + 4096, 0, 7097, + 256, 256, 256, + }, + }; + /* 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 color_matrix_coefs *coefficients = NULL; + const struct blanking_codes *blanking_codes = NULL; + u8 reg; + + v4l_dbg(1, debug, state->client, "%s\n", __func__); + switch (state->vid_fmt) { + /* RGB444 */ + case OF_FMT_444: + blanking_codes = &rgb_blanking; + if (colorspace != HDMI_COLORSPACE_RGB) { + if (colorimetry == HDMI_COLORIMETRY_ITU_709) + coefficients = &conv_matrix[ITU709_RGBLIMITED]; + else + coefficients = &conv_matrix[ITU601_RGBLIMITED]; + } + break; + + /* YUV422 */ + case OF_FMT_422_SMPT: /* semi-planar */ + case OF_FMT_422_CCIR: /* CCIR656 */ + blanking_codes = &yuv_blanking; + if (colorspace == HDMI_COLORSPACE_RGB) + coefficients = &conv_matrix[RGBLIMITED_ITU601]; + break; + } + + if (coefficients) { + /* 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, coefficients->offint1); + io_write16(sd, REG_VDP_MATRIX + 2, coefficients->offint2); + io_write16(sd, REG_VDP_MATRIX + 4, coefficients->offint3); + /* coefficients */ + io_write16(sd, REG_VDP_MATRIX + 6, coefficients->p11coef); + io_write16(sd, REG_VDP_MATRIX + 8, coefficients->p12coef); + io_write16(sd, REG_VDP_MATRIX + 10, coefficients->p13coef); + io_write16(sd, REG_VDP_MATRIX + 12, coefficients->p21coef); + io_write16(sd, REG_VDP_MATRIX + 14, coefficients->p22coef); + io_write16(sd, REG_VDP_MATRIX + 16, coefficients->p23coef); + io_write16(sd, REG_VDP_MATRIX + 18, coefficients->p31coef); + io_write16(sd, REG_VDP_MATRIX + 20, coefficients->p32coef); + io_write16(sd, REG_VDP_MATRIX + 22, coefficients->p33coef); + /* offset outputs */ + io_write16(sd, REG_VDP_MATRIX + 24, coefficients->offout1); + io_write16(sd, REG_VDP_MATRIX + 26, coefficients->offout2); + io_write16(sd, REG_VDP_MATRIX + 28, coefficients->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); + } + + return 0; +} + +/* Configure frame detection window and VHREF timing generator */ +static int +tda1997x_configure_input_resolution(struct v4l2_subdev *sd, + const struct tda1997x_video_std *std) +{ + struct tda1997x_state *state = to_state(sd); + const struct v4l2_bt_timings *bt = &std->timings.bt; + const struct vhref_values *vh = &std->vhref_values; + int width, lines; + u8 reg; + + v4l_dbg(1, debug, state->client, "%s %dx%d%c@%dHz\n", __func__, + bt->width, bt->height, + bt->interlaced ? 'i' : 'p', state->fps); + width = bt->width + bt->hfrontporch + bt->hsync + + bt->hbackporch; + lines = bt->height + bt->vfrontporch + bt->vsync + + bt->vbackporch; + + /* + * 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, vh->href_start & MASK_VHREF); + io_write16(sd, REG_HREF_E, vh->href_end & MASK_VHREF); + /* vertical reference f1 start/end */ + io_write16(sd, REG_VREF_F1_S, vh->vref_f1_start & MASK_VHREF); + io_write(sd, REG_VREF_F1_WIDTH, vh->vref_f1_width); + /* vertical reference f2 start/end */ + io_write16(sd, REG_VREF_F2_S, vh->vref_f2_start & MASK_VHREF); + io_write(sd, REG_VREF_F2_WIDTH, vh->vref_f2_width); + /* F1/F2 FREF, field polarity */ + io_write16(sd, REG_FREF_F1_S, (vh->fieldref_f1_start & MASK_VHREF) + || (vh->fieldPolarity << 8)); + io_write16(sd, REG_FREF_F2_S, vh->fieldref_f2_start & MASK_VHREF); + + return 0; +} + +/* 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/7taps 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 int +tda1997x_detect_std(struct tda1997x_state *state) +{ + 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; + if (!vper || !hper || !hsper) + return -ENOLINK; + + /* look for matching timings */ + for (i = 0; i < ARRAY_SIZE(tda1997x_hdmi_modes); i++) { + const struct tda1997x_video_std *std = &tda1997x_hdmi_modes[i]; + const struct v4l2_bt_timings *bt = &std->timings.bt; + int lines, width, _hper, _vper, _hsper; + int vmin, vmax, hmin, hmax, hsmin, hsmax; + int hmatch, vmatch, hsmatch; + + width = bt->width + bt->hfrontporch + bt->hsync + + bt->hbackporch; + lines = bt->height + bt->vfrontporch + bt->vsync + + bt->vbackporch; + + _hper = (int)bt->pixelclock / (int)width; + _vper = _hper / lines; + _hsper = (int)bt->pixelclock / (int)bt->hsync; + if (bt->interlaced) + _vper *= 2; + /* vper +/- 0.7% */ + vmin = 993 * (27000000 / _vper) / 1000; + vmax = 1007 * (27000000 / _vper) / 1000; + /* hper +/- 0.7% */ + hmin = 993 * (27000000 / _hper) / 1000; + hmax = 1007 * (27000000 / _hper) / 1000; + /* hsper +/- 0.7% */ + hsmin = 993 * (27000000 / _hsper) / 1000; + hsmax = 1007 * (27000000 / _hsper) / 1000; + + vmatch = ((vper <= vmax) && (vper >= vmin)) ? 1 : 0; + hmatch = ((hper <= hmax) && (hper >= hmin)) ? 1 : 0; + if (hmatch && vmatch && hsmatch) { + v4l_info(state->client, + "resolution: %dx%d%c@%d (%d/%d/%d)\n", + bt->width, bt->height, bt->interlaced?'i':'p', + _vper, vper, hper, hsper); + state->fps = (int)bt->pixelclock / (width * lines); + state->std = std; + return 0; + } + } + + v4l_err(state->client, "no resolution match for timings: %d/%d/%d\n", + vper, hper, hsper); + return -EINVAL; +} + +/* 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. + */ +static u8 +tda1997x_read_activity_status_regs(struct v4l2_subdev *sd) +{ + u8 reg, status = 0; + + /* Read CLK_A_STATUS register */ + reg = io_read(sd, REG_CLK_A_STATUS); + /* when stable_clk_x is set to 1, check active_x bit */ + if ((reg & MASK_CLK_STABLE) && !(reg & MASK_CLK_ACTIVE)) + reg &= ~MASK_CLK_STABLE; + status |= ((reg & MASK_CLK_STABLE) >> 2); + + /* Read CLK_B_STATUS register */ + reg = io_read(sd, REG_CLK_B_STATUS); + /* when stable_clk_x is set to 1, check active_x bit */ + if ((reg & MASK_CLK_STABLE) && !(reg & MASK_CLK_ACTIVE)) + reg &= ~MASK_CLK_STABLE; + status |= ((reg & MASK_CLK_STABLE) >> 1); + + /* Read the SUS_STATUS register */ + reg = io_read(sd, REG_SUS_STATUS); + + /* If state = 5 => TMDS is locked */ + if ((reg & MASK_SUS_STATUS) == LAST_STATE_REACHED) + status |= MASK_SUS_STATE; + else + status &= ~MASK_SUS_STATE; + + return status; +} + +/* parse an infoframe and do some sanity checks on it */ +static unsigned int +tda1997x_parse_infoframe(struct tda1997x_state *state, u16 addr) +{ + struct v4l2_subdev *sd = &state->sd; + union hdmi_infoframe frame; + u8 buffer[40]; + u8 reg; + int len, err; + + /* read data */ + len = io_readn(sd, addr, sizeof(buffer), buffer); + err = hdmi_infoframe_unpack(&frame, buffer); + if (err) { + v4l_err(state->client, + "failed parsing %d byte infoframe: 0x%04x/0x%02x\n", + len, addr, buffer[0]); + return err; + } + if (debug > 1) + hdmi_infoframe_log(KERN_INFO, &state->client->dev, &frame); + switch (frame.any.type) { + /* Audio InfoFrame: see HDMI spec 8.2.2 */ + case HDMI_INFOFRAME_TYPE_AUDIO: + /* sample rate */ + switch (frame.audio.sample_frequency) { + case HDMI_AUDIO_SAMPLE_FREQUENCY_32000: + state->audio_samplerate = 32000; + break; + case HDMI_AUDIO_SAMPLE_FREQUENCY_44100: + state->audio_samplerate = 44100; + break; + case HDMI_AUDIO_SAMPLE_FREQUENCY_48000: + state->audio_samplerate = 48000; + break; + case HDMI_AUDIO_SAMPLE_FREQUENCY_88200: + state->audio_samplerate = 88200; + break; + case HDMI_AUDIO_SAMPLE_FREQUENCY_96000: + state->audio_samplerate = 96000; + break; + case HDMI_AUDIO_SAMPLE_FREQUENCY_176400: + state->audio_samplerate = 176400; + break; + case HDMI_AUDIO_SAMPLE_FREQUENCY_192000: + state->audio_samplerate = 192000; + break; + default: + case HDMI_AUDIO_SAMPLE_FREQUENCY_STREAM: + break; + } + + /* sample size */ + switch (frame.audio.sample_size) { + case HDMI_AUDIO_SAMPLE_SIZE_16: + state->audio_samplesize = 16; + break; + case HDMI_AUDIO_SAMPLE_SIZE_20: + state->audio_samplesize = 20; + break; + case HDMI_AUDIO_SAMPLE_SIZE_24: + state->audio_samplesize = 24; + break; + case HDMI_AUDIO_SAMPLE_SIZE_STREAM: + default: + break; + } + + /* Channel Count */ + state->audio_channels = frame.audio.channels; + if (frame.audio.channel_allocation && + frame.audio.channel_allocation != state->audio_ch_alloc) { + /* use the channel assignment from the infoframe */ + state->audio_ch_alloc = frame.audio.channel_allocation; + tda1997x_configure_audout(sd, state->audio_ch_alloc); + /* reset the audio FIFO */ + tda1997x_hdmi_info_reset(sd, RESET_AUDIO, false); + } + break; + + /* Source Product Descriptor information (SPD) */ + case HDMI_INFOFRAME_TYPE_SPD: + strncpy(frame.spd.vendor, state->vendor, + sizeof(frame.spd.vendor)); + strncpy(frame.spd.product, state->product, + sizeof(frame.spd.product)); + v4l_info(state->client, "Source Product Descriptor: %s %s\n", + state->vendor, state->product); + break; + + /* Auxiliary Video information (AVI) InfoFrame: see HDMI spec 8.2.1 */ + case HDMI_INFOFRAME_TYPE_AVI: + state->colorspace = frame.avi.colorspace; + state->colorimetry = frame.avi.colorimetry; + /* + * If colorimetry not specified, conversion depends on res type: + * - SDTV: ITU601 for SD (480/576/240/288 line resolution) + * - HDTV: ITU709 for HD (720/1080 line resolution) + * - PC: sRGB + * see HDMI specification section 6.7 + */ + if ((state->colorspace == HDMI_COLORSPACE_YUV422 || + state->colorspace == HDMI_COLORSPACE_YUV444) && + (state->colorimetry == HDMI_COLORIMETRY_EXTENDED || + state->colorimetry == HDMI_COLORIMETRY_NONE)) { + switch (state->timings.bt.height) { + case 480: + case 576: + case 240: + case 288: + state->colorimetry = HDMI_COLORIMETRY_ITU_601; + break; + case 720: + case 1080: + state->colorimetry = HDMI_COLORIMETRY_ITU_709; + break; + default: + state->colorimetry = HDMI_COLORIMETRY_NONE; + } + } + v4l_dbg(1, debug, state->client, + "Colorspace=%d Colorimetry=%d\n", + state->colorspace, state->colorimetry); + + /* configure upsampler: 0=bypass 1=repeatchroma 2=interpolate */ + reg = io_read(sd, REG_PIX_REPEAT); + reg &= ~PIX_REPEAT_MASK_UP_SEL; + if (state->colorspace == HDMI_COLORSPACE_YUV422) + reg |= (PIX_REPEAT_CHROMA << PIX_REPEAT_SHIFT); + io_write(sd, REG_PIX_REPEAT, reg); + + /* ConfigurePixelRepeater: repeat n-times each pixel */ + reg = io_read(sd, REG_PIX_REPEAT); + reg &= ~PIX_REPEAT_MASK_REP; + reg |= frame.avi.pixel_repeat; + io_write(sd, REG_PIX_REPEAT, reg); + + /* configure the receiver with the new colorspace */ + tda1997x_configure_conv(sd, state->colorspace, + state->colorimetry); + break; + default: + break; + } + return 0; +} + +static void tda1997x_irq_sus(struct tda1997x_state *state, u8 *flags) +{ + struct v4l2_subdev *sd = &state->sd; + u8 reg, source; + + source = io_read(sd, REG_INT_FLG_CLR_SUS); + io_write(sd, REG_INT_FLG_CLR_SUS, source); + v4l_dbg(1, debug, state->client, "%s 0x%02x\n", __func__, source); + + if (source & MASK_MPT) { + /* reset MTP in use flag if set */ + if (state->mptrw_in_progress) + state->mptrw_in_progress = 0; + } + + if (source & MASK_SUS_END) { + /* reset audio FIFO */ + reg = io_read(sd, REG_HDMI_INFO_RST); + reg |= MASK_SR_FIFO_FIFO_CTRL; + io_write(sd, REG_HDMI_INFO_RST, reg); + reg &= ~MASK_SR_FIFO_FIFO_CTRL; + io_write(sd, REG_HDMI_INFO_RST, reg); + + /* reset HDMI flags */ + state->hdmi_status = 0; + } + + /* filter FMT interrupt based on SUS state */ + reg = io_read(sd, REG_SUS_STATUS); + if (((reg & MASK_SUS_STATUS) != LAST_STATE_REACHED) + || (source & MASK_MPT)) { + source &= ~MASK_FMT; + } + + if (source & (MASK_FMT | MASK_SUS_END)) { + reg = io_read(sd, REG_SUS_STATUS); + if ((reg & MASK_SUS_STATUS) != LAST_STATE_REACHED) { + v4l_err(state->client, "BAD SUS STATUS\n"); + return; + } + + /* There is new activity, the status for HDCP repeater state */ + state->state_c5_reached = 0; + + /* Detect the new resolution */ + if (!tda1997x_detect_std(state)) + v4l2_subdev_notify_event(&state->sd, &tda1997x_ev_fmt); + } +} + +static void tda1997x_irq_ddc(struct tda1997x_state *state, u8 *flags) +{ + struct v4l2_subdev *sd = &state->sd; + u8 source; + + source = io_read(sd, REG_INT_FLG_CLR_DDC); + io_write(sd, REG_INT_FLG_CLR_DDC, source); + if (source & MASK_EDID_MTP) { + /* reset MTP in use flag if set */ + if (state->mptrw_in_progress) + state->mptrw_in_progress = 0; + } +} + +static void tda1997x_irq_rate(struct tda1997x_state *state, u8 *flags) +{ + struct v4l2_subdev *sd = &state->sd; + u8 reg, source; + + u8 irq_status, last_irq_status; + + source = io_read(sd, REG_INT_FLG_CLR_RATE); + io_write(sd, REG_INT_FLG_CLR_RATE, source); + + /* read status regs */ + last_irq_status = irq_status = tda1997x_read_activity_status_regs(sd); + + /* + * read clock status reg until INT_FLG_CLR_RATE is still 0 + * after the read to make sure its the last one + */ + reg = source; + while (reg != 0) { + irq_status = tda1997x_read_activity_status_regs(sd); + reg = io_read(sd, REG_INT_FLG_CLR_RATE); + io_write(sd, REG_INT_FLG_CLR_RATE, reg); + source |= reg; + } + + /* we only pay attention to stability change events */ + if (source & (MASK_RATE_A_ST | MASK_RATE_B_ST)) { + int input = (source & MASK_RATE_A_ST)?0:1; + u8 mask = 1<<input; + + /* state change */ + if ((irq_status & mask) != (state->activity_status & mask)) { + /* activity lost */ + if ((irq_status & mask) == 0) { + v4l_info(state->client, + "HDMI-%c: Digital Activity Lost\n", + input+'A'); + + /* bypass up/down sampler and pixel repeater */ + reg = io_read(sd, REG_PIX_REPEAT); + reg &= ~PIX_REPEAT_MASK_UP_SEL; + reg &= ~PIX_REPEAT_MASK_REP; + io_write(sd, REG_PIX_REPEAT, reg); + + if (state->chip_revision == 0) + tda1997x_reset_n1(state); + + state->std = NULL; + state->fps = 0; + state->input_detect[input] = 0; + v4l2_subdev_notify_event(sd, &tda1997x_ev_fmt); + } + + /* activity detected */ + else { + v4l_info(state->client, + "HDMI-%c: Digital Activity Detected\n", + input+'A'); + state->input_detect[input] = 1; + } + + /* hold onto current state */ + state->activity_status = (irq_status & mask); + } + } +} + +static void tda1997x_irq_info(struct tda1997x_state *state, u8 *flags) +{ + struct v4l2_subdev *sd = &state->sd; + u8 source; + + source = io_read(sd, REG_INT_FLG_CLR_INFO); + io_write(sd, REG_INT_FLG_CLR_INFO, source); + + /* Audio infoframe */ + if (source & MASK_AUD_IF) { + tda1997x_parse_infoframe(state, AUD_IF); + source &= ~MASK_AUD_IF; + } + + /* Source Product Descriptor infoframe change */ + if (source & MASK_SPD_IF) { + tda1997x_parse_infoframe(state, SPD_IF); + source &= ~MASK_SPD_IF; + } + + /* Auxiliary Video Information infoframe */ + if (source & MASK_AVI_IF) { + tda1997x_parse_infoframe(state, AVI_IF); + source &= ~MASK_AVI_IF; + } +} + +static void tda1997x_irq_audio(struct tda1997x_state *state, u8 *flags) +{ + struct v4l2_subdev *sd = &state->sd; + u8 reg, source; + + source = io_read(sd, REG_INT_FLG_CLR_AUDIO); + io_write(sd, REG_INT_FLG_CLR_AUDIO, source); + + /* reset audio FIFO on FIFO pointer error or audio mute */ + if (source & MASK_ERROR_FIFO_PT || + source & MASK_MUTE_FLG) { + /* audio reset audio FIFO */ + reg = io_read(sd, REG_SUS_STATUS); + if ((reg & MASK_SUS_STATUS) == LAST_STATE_REACHED) { + reg = io_read(sd, REG_HDMI_INFO_RST); + reg |= MASK_SR_FIFO_FIFO_CTRL; + io_write(sd, REG_HDMI_INFO_RST, reg); + reg &= ~MASK_SR_FIFO_FIFO_CTRL; + io_write(sd, REG_HDMI_INFO_RST, reg); + /* reset channel status IT if present */ + source &= ~(MASK_CH_STATE); + } + } + if (source & MASK_AUDIO_FREQ_FLG) { + static const int freq[] = { + 0, 32000, 44100, 48000, 88200, 96000, 176400, 192000 + }; + + reg = io_read(sd, REG_AUDIO_FREQ); + state->audio_samplerate = freq[reg & 7]; + v4l_info(state->client, "Audio Frequency Change: %dHz\n", + state->audio_samplerate); + } + if (source & MASK_AUDIO_FLG) { + reg = io_read(sd, REG_AUDIO_FLAGS); + if (reg & BIT(AUDCFG_TYPE_DST)) + state->audio_type = AUDCFG_TYPE_DST; + if (reg & BIT(AUDCFG_TYPE_OBA)) + state->audio_type = AUDCFG_TYPE_OBA; + if (reg & BIT(AUDCFG_TYPE_HBR)) + state->audio_type = AUDCFG_TYPE_HBR; + if (reg & BIT(AUDCFG_TYPE_PCM)) + state->audio_type = AUDCFG_TYPE_PCM; + v4l_info(state->client, "Audio Type: %s\n", + audtype_names[state->audio_type]); + } +} + +static void tda1997x_irq_hdcp(struct tda1997x_state *state, u8 *flags) +{ + struct v4l2_subdev *sd = &state->sd; + u8 reg, source; + + source = io_read(sd, REG_INT_FLG_CLR_HDCP); + io_write(sd, REG_INT_FLG_CLR_HDCP, source); + + /* reset MTP in use flag if set */ + if (source & MASK_HDCP_MTP) + state->mptrw_in_progress = 0; + if (source & MASK_STATE_C5) { + /* REPEATER: mask AUDIO and IF irqs to avoid IF during auth */ + reg = io_read(sd, REG_INT_MASK_TOP); + reg &= ~(INTERRUPT_AUDIO | INTERRUPT_INFO); + io_write(sd, REG_INT_MASK_TOP, reg); + *flags &= (INTERRUPT_AUDIO | INTERRUPT_INFO); + } +} + +static irqreturn_t tda1997x_isr_thread(int irq, void *d) +{ + struct tda1997x_state *state = d; + struct v4l2_subdev *sd = &state->sd; + u8 flags; + + mutex_lock(&state->lock); + do { + /* read interrupt flags */ + flags = io_read(sd, REG_INT_FLG_CLR_TOP); + if (flags == 0) + break; + + /* SUS interrupt source (Input activity events) */ + if (flags & INTERRUPT_SUS) + tda1997x_irq_sus(state, &flags); + /* DDC interrupt source (Display Data Channel) */ + else if (flags & INTERRUPT_DDC) + tda1997x_irq_ddc(state, &flags); + /* RATE interrupt source (Digital Input activity) */ + else if (flags & INTERRUPT_RATE) + tda1997x_irq_rate(state, &flags); + /* Infoframe change interrupt */ + else if (flags & INTERRUPT_INFO) + tda1997x_irq_info(state, &flags); + /* Audio interrupt source: + * freq change, DST,OBA,HBR,ASP flags, mute, FIFO err + */ + else if (flags & INTERRUPT_AUDIO) + tda1997x_irq_audio(state, &flags); + /* HDCP interrupt source (content protection) */ + if (flags & INTERRUPT_HDCP) + tda1997x_irq_hdcp(state, &flags); + } while (flags != 0); + mutex_unlock(&state->lock); + + return IRQ_HANDLED; +} + +static bool tda1997x_check_dv_timings(const struct v4l2_dv_timings *timings, + void *hdl) +{ + const struct tda1997x_video_std *std; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(tda1997x_hdmi_modes); i++) { + std = &tda1997x_hdmi_modes[i]; + if (v4l2_match_dv_timings(timings, &std->timings, 0, false)) + return true; + } + + return false; +} + +/* ----------------------------------------------------------------------------- + * v4l2_subdev_video_ops + */ + +static int +tda1997x_g_input_status(struct v4l2_subdev *sd, u32 *status) +{ + struct tda1997x_state *state = to_state(sd); + + mutex_lock(&state->lock); + if (state->std) + *status = 0; + else + *status |= V4L2_IN_ST_NO_SIGNAL; + mutex_unlock(&state->lock); + + return 0; +}; + +static int tda1997x_s_dv_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + struct tda1997x_state *state = to_state(sd); + int ret; + + v4l_dbg(1, debug, state->client, "%s\n", __func__); + if (!timings) + return -EINVAL; + + if (v4l2_match_dv_timings(&state->timings, timings, 0, false)) + return 0; /* no changes */ + + if (!v4l2_valid_dv_timings(timings, &tda1997x_dv_timings_cap, + tda1997x_check_dv_timings, state)) + return -ERANGE; + + mutex_lock(&state->lock); + state->timings = *timings; + /* setup frame detection window and VHREF timing generator */ + ret = tda1997x_configure_input_resolution(sd, state->std); + if (ret) + goto error; + ret = tda1997x_configure_conv(sd, state->colorspace, + state->colorimetry); + if (ret) + goto error; + mutex_unlock(&state->lock); + + return 0; + +error: + mutex_unlock(&state->lock); + return ret; +} + +static int tda1997x_g_dv_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + struct tda1997x_state *state = to_state(sd); + + v4l_dbg(1, debug, state->client, "%s\n", __func__); + if (!timings) + return -EINVAL; + + mutex_lock(&state->lock); + *timings = state->timings; + mutex_unlock(&state->lock); + + return 0; +} + +static int tda1997x_query_dv_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + struct tda1997x_state *state = to_state(sd); + int ret; + + v4l_dbg(1, debug, state->client, "%s\n", __func__); + if (!timings) + return -EINVAL; + + memset(timings, 0, sizeof(struct v4l2_dv_timings)); + mutex_lock(&state->lock); + ret = tda1997x_detect_std(state); + if (ret) + goto error; + *timings = state->std->timings; + mutex_unlock(&state->lock); + return 0; + +error: + mutex_unlock(&state->lock); + return ret; +} + +static int tda1997x_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct tda1997x_state *state = to_state(sd); + + v4l_dbg(1, debug, state->client, "%s %d\n", __func__, enable); + mutex_lock(&state->lock); + if (!state->std) + v4l_dbg(1, debug, state->client, "Invalid HDMI signal\n"); + mutex_unlock(&state->lock); + + return 0; +} + +static const struct v4l2_subdev_video_ops tda1997x_video_ops = { + .g_input_status = tda1997x_g_input_status, + .s_dv_timings = tda1997x_s_dv_timings, + .g_dv_timings = tda1997x_g_dv_timings, + .query_dv_timings = tda1997x_query_dv_timings, + .s_stream = tda1997x_s_stream, +}; + + +/* ----------------------------------------------------------------------------- + * v4l2_subdev_pad_ops + */ + +static int tda1997x_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct tda1997x_state *state = to_state(sd); + + if (code->index > 0) + return -EINVAL; + + code->code = state->code; + + return 0; +} + +static int tda1997x_fill_format(struct tda1997x_state *state, + struct v4l2_mbus_framefmt *format) +{ + v4l_dbg(1, debug, state->client, "%s\n", __func__); + if (!state->std) + return -EINVAL; + memset(format, 0, sizeof(*format)); + + format->width = state->std->timings.bt.width; + format->height = state->std->timings.bt.height; + format->field = V4L2_FIELD_NONE; + format->colorspace = V4L2_COLORSPACE_SRGB; + if (state->timings.bt.flags & V4L2_DV_FL_IS_CE_VIDEO) + format->colorspace = (state->std->timings.bt.height <= 576) ? + V4L2_COLORSPACE_SMPTE170M : V4L2_COLORSPACE_REC709; + + return 0; +} + +static int tda1997x_get_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct tda1997x_state *state = to_state(sd); + + v4l_dbg(1, debug, state->client, "%s\n", __func__); + if (format->pad != TDA1997X_PAD_SOURCE) + return -EINVAL; + + tda1997x_fill_format(state, &format->format); + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { + struct v4l2_mbus_framefmt *fmt; + + fmt = v4l2_subdev_get_try_format(sd, cfg, format->pad); + format->format.code = format->format.code; + } else + format->format.code = state->code; + + return 0; +} + +static int tda1997x_set_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *fmt; + + if (format->pad != TDA1997X_PAD_SOURCE) + return -EINVAL; + + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) + return tda1997x_get_pad_format(sd, cfg, format); + + fmt = v4l2_subdev_get_try_format(sd, cfg, format->pad); + *fmt = format->format; + + return 0; +} + +static int tda1997x_get_edid(struct v4l2_subdev *sd, struct v4l2_edid *edid) +{ + struct tda1997x_state *state = to_state(sd); + + v4l_dbg(1, debug, state->client, "%s pad=%d\n", __func__, edid->pad); + memset(edid->reserved, 0, sizeof(edid->reserved)); + + if (!state->edid.present) + return -ENODATA; + + if (edid->start_block == 0 && edid->blocks == 0) { + edid->blocks = state->edid.blocks; + return 0; + } + + if (edid->start_block >= state->edid.blocks) + return -EINVAL; + + if (edid->start_block + edid->blocks > state->edid.blocks) + edid->blocks = state->edid.blocks - edid->start_block; + + memcpy(edid->edid, state->edid.edid + edid->start_block * 128, + edid->blocks * 128); + + return 0; +} + +static int tda1997x_set_edid(struct v4l2_subdev *sd, struct v4l2_edid *edid) +{ + struct tda1997x_state *state = to_state(sd); + int i; + + v4l_dbg(1, debug, state->client, "%s pad=%d\n", __func__, edid->pad); + memset(edid->reserved, 0, sizeof(edid->reserved)); + + if (edid->start_block != 0) + return -EINVAL; + + if (edid->blocks == 0) { + state->edid.blocks = 0; + state->edid.present = 0; + tda1997x_manual_hpd(&state->sd, HPD_LOW_BP); + return 0; + } + + if (edid->blocks > 2) { + edid->blocks = 2; + return -E2BIG; + } + + /* write base EDID */ + for (i = 0; i < 128; i++) + io_write(sd, REG_EDID_IN_BYTE0 + i, edid->edid[i]); + + /* write CEA Extension */ + for (i = 0; i < 128; i++) + io_write(sd, REG_EDID_IN_BYTE128 + i, edid->edid[i+128]); + + return 0; +} + +static int tda1997x_get_dv_timings_cap(struct v4l2_subdev *sd, + struct v4l2_dv_timings_cap *cap) +{ + *cap = tda1997x_dv_timings_cap; + return 0; +} + +static int tda1997x_enum_dv_timings(struct v4l2_subdev *sd, + struct v4l2_enum_dv_timings *timings) +{ + struct tda1997x_state *state = to_state(sd); + + return v4l2_enum_dv_timings_cap(timings, &tda1997x_dv_timings_cap, + tda1997x_check_dv_timings, state); +} + +static const struct v4l2_subdev_pad_ops tda1997x_pad_ops = { + .enum_mbus_code = tda1997x_enum_mbus_code, + .get_fmt = tda1997x_get_pad_format, + .set_fmt = tda1997x_set_pad_format, + .get_edid = tda1997x_get_edid, + .set_edid = tda1997x_set_edid, + .dv_timings_cap = tda1997x_get_dv_timings_cap, + .enum_dv_timings = tda1997x_enum_dv_timings, +}; + +/* ----------------------------------------------------------------------------- + * v4l2_subdev_core_ops + */ + +static int tda1997x_log_status(struct v4l2_subdev *sd) +{ + struct tda1997x_state *state = to_state(sd); + const struct v4l2_dv_timings *timings = &state->timings; + + v4l2_info(sd, "-----Signal status-----\n"); + if (!timings) { + v4l2_info(sd, "no signal\n"); + return 0; + } + v4l2_info(sd, "resolution: %dx%d%c@%dHz\n", + timings->bt.width, timings->bt.height, + timings->bt.interlaced ? 'i' : 'p', + state->fps); + v4l2_print_dv_timings(sd->name, "Detected format: ", + timings, true); + v4l2_info(sd, "colorspace: %d\n", state->colorspace); + v4l2_info(sd, "colorimetry: %d\n", state->colorimetry); + if (state->audio_channels) + v4l2_info(sd, "audio: %dch %dHz\n", state->audio_channels, + state->audio_samplerate); + else + v4l2_info(sd, "audio: none\n"); + v4l2_info(sd, "vendor: %s\n", state->vendor); + v4l2_info(sd, "product: %s\n", state->product); + + return 0; +} + +static int tda1997x_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 tda1997x_core_ops = { + .log_status = tda1997x_log_status, + .subscribe_event = tda1997x_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +}; + +/* ----------------------------------------------------------------------------- + * v4l2_subdev_ops + */ + +static const struct v4l2_subdev_ops tda1997x_subdev_ops = { + .core = &tda1997x_core_ops, + .video = &tda1997x_video_ops, + .pad = &tda1997x_pad_ops, +}; + +static int tda1997x_core_init(struct v4l2_subdev *sd) +{ + struct tda1997x_state *state = to_state(sd); + struct tda1997x_platform_data *pdata = &state->pdata; + u8 reg; + int i; + + /* disable HPD */ + io_write(sd, REG_HPD_AUTO_CTRL, HPD_AUTO_HPD_UNSEL); + if (state->chip_revision == 0) { + io_write(sd, REG_MAN_SUS_HDMI_SEL, MAN_DIS_HDCP | MAN_RST_HDCP); + io_write(sd, REG_CGU_DBG_SEL, 1 << CGU_DBG_CLK_SEL_SHIFT); + } + + /* reset infoframe at end of start-up-sequencer */ + io_write(sd, REG_SUS_SET_RGB2, 0x06); + io_write(sd, REG_SUS_SET_RGB3, 0x06); + + /* Enable TMDS pull-ups */ + io_write(sd, REG_RT_MAN_CTRL, RT_MAN_CTRL_RT | + RT_MAN_CTRL_RT_B | RT_MAN_CTRL_RT_A); + + /* enable sync measurement timing */ + tda1997x_cec_write(sd, REG_PWR_CONTROL & 0xff, 0x04); + /* adjust CEC clock divider */ + tda1997x_cec_write(sd, REG_OSC_DIVIDER & 0xff, 0x03); + tda1997x_cec_write(sd, REG_EN_OSC_PERIOD_LSB & 0xff, 0xa0); + io_write(sd, REG_TIMER_D, 0x54); + /* enable power switch */ + reg = tda1997x_cec_read(sd, REG_CONTROL & 0xff); + reg |= 0x20; + tda1997x_cec_write(sd, REG_CONTROL & 0xff, reg); + mdelay(50); + + /* read the chip version */ + reg = io_read(sd, REG_VERSION); + /* get the chip configuration */ + reg = io_read(sd, REG_CMTP_REG10); + + /* enable interrupts we care about */ + io_write(sd, REG_INT_MASK_TOP, + INTERRUPT_HDCP | INTERRUPT_AUDIO | INTERRUPT_INFO | + INTERRUPT_RATE | INTERRUPT_SUS); + /* config_mtp,fmt,sus_end,sus_st */ + io_write(sd, REG_INT_MASK_SUS, MASK_MPT | MASK_FMT | MASK_SUS_END); + /* rate stability change for inputs A/B */ + io_write(sd, REG_INT_MASK_RATE, MASK_RATE_B_ST | MASK_RATE_A_ST); + /* aud,spd,avi*/ + io_write(sd, REG_INT_MASK_INFO, + MASK_AUD_IF | MASK_SPD_IF | MASK_AVI_IF); + /* audio_freq,audio_flg,mute_flg,fifo_err */ + io_write(sd, REG_INT_MASK_AUDIO, + MASK_AUDIO_FREQ_FLG | MASK_AUDIO_FLG | MASK_MUTE_FLG | + MASK_ERROR_FIFO_PT); + /* HDCP C5 state reached */ + io_write(sd, REG_INT_MASK_HDCP, MASK_STATE_C5); + /* don't care about AFE/DDC/MODE */ + io_write(sd, REG_INT_MASK_AFE, 0); + io_write(sd, REG_INT_MASK_DDC, 0); + io_write(sd, REG_INT_MASK_MODE, 0); + + /* clear all interrupts */ + io_write(sd, REG_INT_FLG_CLR_TOP, 0xff); + io_write(sd, REG_INT_FLG_CLR_SUS, 0xff); + io_write(sd, REG_INT_FLG_CLR_DDC, 0xff); + io_write(sd, REG_INT_FLG_CLR_RATE, 0xff); + io_write(sd, REG_INT_FLG_CLR_MODE, 0xff); + io_write(sd, REG_INT_FLG_CLR_INFO, 0xff); + io_write(sd, REG_INT_FLG_CLR_AUDIO, 0xff); + io_write(sd, REG_INT_FLG_CLR_HDCP, 0xff); + io_write(sd, REG_INT_FLG_CLR_AFE, 0xff); + + /* init TMDS equalizer */ + if (state->chip_revision == 0) + io_write(sd, REG_CGU_DBG_SEL, 1 << CGU_DBG_CLK_SEL_SHIFT); + io_write24(sd, REG_CLK_MIN_RATE, CLK_MIN_RATE); + io_write24(sd, REG_CLK_MAX_RATE, CLK_MAX_RATE); + if (state->chip_revision == 0) + io_write(sd, REG_WDL_CFG, WDL_CFG_VAL); + /* DC filter */ + io_write(sd, REG_DEEP_COLOR_CTRL, DC_FILTER_VAL); + /* disable test pattern */ + io_write(sd, REG_SVC_MODE, 0x00); + /* update HDMI INFO CTRL */ + io_write(sd, REG_INFO_CTRL, 0xff); + /* write HDMI INFO EXCEED value */ + io_write(sd, REG_INFO_EXCEED, 3); + + if (state->chip_revision == 0) + tda1997x_reset_n1(state); + + /* + * No HDCP acknowledge when HDCP is disabled + * and reset SUS to force format detection + */ + tda1997x_hdmi_info_reset(sd, NACK_HDCP, true); + + /* Set HPD low */ + tda1997x_manual_hpd(sd, HPD_LOW_BP); + + /* Configure receiver capabilities */ + io_write(sd, REG_HDCP_BCAPS, HDCP_HDMI | HDCP_FAST_REAUTH); + + /* Configure HDMI: Auto HDCP mode, packet controlled mute */ + reg = HDMI_CTRL_MUTE_AUTO << HDMI_CTRL_MUTE_SHIFT; + reg |= HDMI_CTRL_HDCP_AUTO << HDMI_CTRL_HDCP_SHIFT; + io_write(sd, REG_HDMI_CTRL, reg); + + /* reset start-up-sequencer to force format detection */ + tda1997x_hdmi_info_reset(sd, 0, true); + + /* Set HPD high */ + tda1997x_manual_hpd(sd, HPD_HIGH_OTHER); + tda1997x_manual_hpd(sd, HPD_HIGH_BP); + + /* disable matrix conversion */ + reg = io_read(sd, REG_VDP_CTRL); + reg |= VDP_CTRL_MATRIX_BP; + io_write(sd, REG_VDP_CTRL, reg); + + /* set video output mode */ + tda1997x_configure_vidout(state); + + /* configure video output port */ + for (i = 0; i < 9; i++) { + v4l_dbg(1, debug, state->client, "vidout_cfg[%d]=0x%02x\n", i, + pdata->vidout_port_cfg[i]); + io_write(sd, REG_VP35_32_CTRL + i, pdata->vidout_port_cfg[i]); + } + + /* configure audio output port */ + tda1997x_configure_audout(sd, 0); + + /* configure audio clock freq */ + switch (pdata->audout_mclk_fs) { + case 512: + reg = AUDIO_CLOCK_SEL_512FS; + break; + case 256: + reg = AUDIO_CLOCK_SEL_256FS; + break; + case 128: + reg = AUDIO_CLOCK_SEL_128FS; + break; + case 64: + reg = AUDIO_CLOCK_SEL_64FS; + break; + case 32: + reg = AUDIO_CLOCK_SEL_32FS; + break; + default: + reg = AUDIO_CLOCK_SEL_16FS; + break; + } + io_write(sd, REG_AUDIO_CLOCK, reg); + + /* reset advanced infoframes (ISRC1/ISRC2/ACP) */ + tda1997x_hdmi_info_reset(sd, RESET_AI, false); + /* reset infoframe */ + tda1997x_hdmi_info_reset(sd, RESET_IF, false); + /* reset audio infoframes */ + tda1997x_hdmi_info_reset(sd, RESET_AUDIO, false); + /* reset gamut */ + tda1997x_hdmi_info_reset(sd, RESET_GAMUT, false); + + /* get initial HDMI status */ + state->hdmi_status = io_read(sd, REG_HDMI_FLAGS); + + return 0; +} + +static int tda1997x_set_power(struct tda1997x_state *state, bool on) +{ + int ret = 0; + + if (on) { + ret = regulator_bulk_enable(TDA1997X_NUM_SUPPLIES, + state->supplies); + msleep(300); + } + + ret = regulator_bulk_disable(TDA1997X_NUM_SUPPLIES, + state->supplies); + return ret; +} + +static const struct i2c_device_id tda1997x_i2c_id[] = { + {"tda19971", (kernel_ulong_t)&tda1997x_chip_info[TDA19971]}, + {"tda19973", (kernel_ulong_t)&tda1997x_chip_info[TDA19973]}, + { }, +}; +MODULE_DEVICE_TABLE(i2c, tda1997x_i2c_id); + +static const struct of_device_id tda1997x_of_id[] __maybe_unused = { + { .compatible = "nxp,tda19971", .data = &tda1997x_chip_info[TDA19971] }, + { .compatible = "nxp,tda19973", .data = &tda1997x_chip_info[TDA19973] }, + { }, +}; +MODULE_DEVICE_TABLE(of, tda1997x_of_id); + +static int tda1997x_parse_dt(struct tda1997x_state *state) +{ + struct tda1997x_platform_data *pdata = &state->pdata; + struct v4l2_fwnode_endpoint bus_cfg; + struct device_node *ep; + struct device_node *np; + unsigned int flags; + const char *str; + int ret; + u32 v; + + /* + * setup default values: + * - HREF: active high from start to end of row + * - VS: Vertical Sync active high at beginning of frame + * - DE: Active high when data valid + * - A_CLK: 128*Fs + */ + pdata->vidout_sel_hs = HS_HREF_SEL_HREF_VHREF; + pdata->vidout_sel_vs = VS_VREF_SEL_VREF_HDMI; + pdata->vidout_sel_de = DE_FREF_SEL_DE_VHREF; + + np = state->client->dev.of_node; + ep = of_graph_get_next_endpoint(np, NULL); + if (!ep) + return -EINVAL; + + ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep), &bus_cfg); + if (ret) { + of_node_put(ep); + return ret; + } + of_node_put(ep); + + /* polarity of HS/VS/DE */ + flags = bus_cfg.bus.parallel.flags; + if (flags & V4L2_MBUS_HSYNC_ACTIVE_LOW) + pdata->vidout_inv_hs = 1; + if (flags & V4L2_MBUS_VSYNC_ACTIVE_LOW) + pdata->vidout_inv_vs = 1; + if (flags & V4L2_MBUS_DATA_ACTIVE_LOW) + pdata->vidout_inv_de = 1; + pdata->vidout_bus_width = bus_cfg.bus.parallel.bus_width; + + /* video output port config */ + ret = of_property_count_u32_elems(np, "nxp,vidout-portcfg"); + if (ret > 0) { + u32 reg, val, i; + + for (i = 0; i < ret / 2 && i < 9; i++) { + of_property_read_u32_index(np, "nxp,vidout-portcfg", + i * 2, ®); + of_property_read_u32_index(np, "nxp,vidout-portcfg", + i * 2 + 1, &val); + if (reg < 9) + pdata->vidout_port_cfg[reg] = val; + } + } else { + v4l_err(state->client, "nxp,vidout-portcfg missing\n"); + return -EINVAL; + } + + /* default to channel layout dictated by packet header */ + pdata->audout_layoutauto = true; + + pdata->audout_format = AUDFMT_TYPE_DISABLED; + if (!of_property_read_string(np, "nxp,audout-format", &str)) { + if (strcmp(str, "i2s") == 0) + pdata->audout_format = AUDFMT_TYPE_I2S; + else if (strcmp(str, "spdif") == 0) + pdata->audout_format = AUDFMT_TYPE_SPDIF; + else { + v4l_err(state->client, "nxp,audout-format invalid\n"); + return -EINVAL; + } + if (!of_property_read_u32(np, "nxp,audout-layout", &v)) { + switch (v) { + case 0: + case 1: + break; + default: + v4l_err(state->client, + "nxp,audout-layout invalid\n"); + return -EINVAL; + } + pdata->audout_layout = v; + } + if (!of_property_read_u32(np, "nxp,audout-width", &v)) { + switch (v) { + case 16: + case 32: + break; + default: + v4l_err(state->client, + "nxp,audout-width invalid\n"); + return -EINVAL; + } + pdata->audout_width = v; + } + if (!of_property_read_u32(np, "nxp,audout-mclk-fs", &v)) { + switch (v) { + case 512: + case 256: + case 128: + case 64: + case 32: + case 16: + break; + default: + v4l_err(state->client, + "nxp,audout-mclk-fs invalid\n"); + return -EINVAL; + } + pdata->audout_mclk_fs = v; + } + } + + return 0; +} + +static int tda1997x_get_regulators(struct tda1997x_state *state) +{ + int i; + + for (i = 0; i < TDA1997X_NUM_SUPPLIES; i++) + state->supplies[i].supply = tda1997x_supply_name[i]; + + return devm_regulator_bulk_get(&state->client->dev, + TDA1997X_NUM_SUPPLIES, + state->supplies); +} + +static int tda1997x_identify_module(struct tda1997x_state *state) +{ + struct v4l2_subdev *sd = &state->sd; + enum tda1997x_type type; + u8 reg; + + /* Read chip configuration*/ + reg = io_read(sd, REG_CMTP_REG10); + state->tmdsb_clk = (reg >> 6) & 0x01; /* use tmds clock B_inv for B */ + state->tmdsb_soc = (reg >> 5) & 0x01; /* tmds of input B */ + state->port_30bit = (reg >> 2) & 0x03; /* 30bit vs 24bit */ + state->output_2p5 = (reg >> 1) & 0x01; /* output supply 2.5v */ + switch ((reg >> 4) & 0x03) { + case 0x00: + type = TDA19971; + break; + case 0x02: + case 0x03: + type = TDA19973; + break; + default: + dev_err(&state->client->dev, "unsupported chip ID\n"); + return -EIO; + } + if (state->info->type != type) { + dev_err(&state->client->dev, "chip id mismatch\n"); + return -EIO; + } + + /* read chip revision */ + state->chip_revision = io_read(sd, REG_CMTP_REG11); + + return 0; +} + +static const struct media_entity_operations tda1997x_media_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + + +/* ----------------------------------------------------------------------------- + * HDMI Audio Codec + */ + +/* refine sample-rate based on HDMI source */ +static int tda1997x_pcm_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct tda1997x_state *state = snd_soc_dai_get_drvdata(dai); + struct snd_soc_codec *codec = dai->codec; + struct snd_pcm_runtime *rtd = substream->runtime; + int rate, err; + + rate = state->audio_samplerate; + err = snd_pcm_hw_constraint_minmax(rtd, SNDRV_PCM_HW_PARAM_RATE, + rate, rate); + if (err < 0) { + dev_err(codec->dev, "failed to constrain samplerate to %dHz\n", + rate); + return err; + } + dev_info(codec->dev, "set samplerate constraint to %dHz\n", rate); + + return 0; +} + +static const struct snd_soc_dai_ops tda1997x_dai_ops = { + .startup = tda1997x_pcm_startup, +}; + +static struct snd_soc_dai_driver tda1997x_audio_dai = { + .name = "tda1997x", + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 | + SNDRV_PCM_RATE_192000, + }, + .ops = &tda1997x_dai_ops, +}; + +static int tda1997x_codec_probe(struct snd_soc_codec *codec) +{ + return 0; +} + +static int tda1997x_codec_remove(struct snd_soc_codec *codec) +{ + return 0; +} + +static struct snd_soc_codec_driver tda1997x_codec_driver = { + .probe = tda1997x_codec_probe, + .remove = tda1997x_codec_remove, + .reg_word_size = sizeof(u16), +}; + +static int tda1997x_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct tda1997x_state *state; + struct tda1997x_platform_data *pdata; + struct v4l2_subdev *sd; + int ret; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + state = kzalloc(sizeof(struct tda1997x_state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + state->client = client; + pdata = &state->pdata; + if (IS_ENABLED(CONFIG_OF) && client->dev.of_node) { + const struct of_device_id *oid; + + oid = of_match_node(tda1997x_of_id, client->dev.of_node); + state->info = oid->data; + + ret = tda1997x_parse_dt(state); + if (ret < 0) { + v4l_err(client, "DT parsing error\n"); + goto err_free_state; + } + } else if (client->dev.platform_data) { + struct tda1997x_platform_data *pdata = + client->dev.platform_data; + state->info = + (const struct tda1997x_chip_info *)id->driver_data; + state->pdata = *pdata; + } else { + v4l_err(client, "No platform data\n"); + ret = -ENODEV; + goto err_free_state; + } + + ret = tda1997x_get_regulators(state); + if (ret) + goto err_free_state; + + ret = tda1997x_set_power(state, 1); + if (ret) + goto err_free_state; + + mutex_init(&state->page_lock); + mutex_init(&state->lock); + state->page = 0xff; + + /* set video format based on chip and bus width */ + ret = tda1997x_identify_module(state); + if (ret) + goto err_free_mutex; + + /* initialize subdev */ + sd = &state->sd; + v4l2_i2c_subdev_init(sd, client, &tda1997x_subdev_ops); + snprintf(sd->name, sizeof(sd->name), "%s %d-%04x", + id->name, i2c_adapter_id(client->adapter), + client->addr); + sd->flags = V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; + sd->entity.ops = &tda1997x_media_ops; + + switch (state->info->type) { + case TDA19973: + switch (pdata->vidout_bus_width) { + case 36: /* 36bit RGB (could also be 36bit YUV) */ + state->code = MEDIA_BUS_FMT_YUV12_1X36; + break; + case 24: /* 24bit BT656 (YUV422 semi-planar: 1-cycle) */ + state->code = MEDIA_BUS_FMT_UYVY12_1X24; + break; + case 12: /* 12bit BT656 (2-cycle) */ + state->code = MEDIA_BUS_FMT_UYVY12_2X12; + break; + } + break; + case TDA19971: + switch (pdata->vidout_bus_width) { + case 24: /* 24bit YUV (could also be RGB or YUV422) */ + state->code = MEDIA_BUS_FMT_YUV8_1X24; + break; + case 20: /* 20bit YUV422 */ + state->code = MEDIA_BUS_FMT_UYVY10_1X20; + break; + case 16: /* 16bit BT656 (YUV422 semi-planar: 1-cycle) */ + state->code = MEDIA_BUS_FMT_UYVY8_1X16; + break; + case 12: /* 12bit BT656 (2-cycle) */ + state->code = MEDIA_BUS_FMT_UYVY12_2X12; + break; + case 10: /* 10bit BT656 (2-cycle) */ + state->code = MEDIA_BUS_FMT_UYVY10_2X10; + break; + case 8: /* 8bit BT656 (2-cycle) */ + state->code = MEDIA_BUS_FMT_UYVY8_2X8; + break; + } + } + switch (state->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->bus_type = V4L2_MBUS_PARALLEL; + state->vid_fmt = OF_FMT_444; + break; + case MEDIA_BUS_FMT_UYVY12_1X24: + case MEDIA_BUS_FMT_UYVY8_1X16: + state->bus_type = V4L2_MBUS_BT656; + 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->bus_type = V4L2_MBUS_BT656; + state->vid_fmt = OF_FMT_422_CCIR; + break; + default: + v4l_err(state->client, "incompatible code (%x)\n", state->code); + ret = -EINVAL; + goto err_free_mutex; + } + state->colorspace = HDMI_COLORSPACE_RGB; + state->colorimetry = HDMI_COLORIMETRY_NONE; + + /* disable/reset HDCP to get correct I2C access to Rx HDMI */ + io_write(sd, REG_MAN_SUS_HDMI_SEL, MAN_RST_HDCP | MAN_DIS_HDCP); + + /* + * if N2 version, reset compdel_bp as it may generate some small pixel + * shifts in case of embedded sync/or delay lower than 4 + */ + if (state->chip_revision != 0) { + io_write(sd, REG_MAN_SUS_HDMI_SEL, 0x00); + io_write(sd, REG_VDP_CTRL, 0x1f); + } + + v4l_info(client, "NXP %s N%d detected\n", state->info->name, + state->chip_revision + 1); + v4l_info(client, "video: %dbit %s %s MBUS_FMT=0x%x\n", + pdata->vidout_bus_width, + (state->bus_type == V4L2_MBUS_PARALLEL) ? "parallel" : "BT656", + vidfmt_names[state->vid_fmt & OF_FMT_MASK], + state->code); + if (pdata->audout_format) { + v4l_info(client, "audio: %dch %s layout%d sysclk=%d*fs\n", + pdata->audout_layout ? 2 : 8, + audfmt_names[pdata->audout_format], + pdata->audout_layout, + pdata->audout_mclk_fs); + } + + ret = 0x34 + ((io_read(sd, REG_SLAVE_ADDR)>>4) & 0x03); + state->client_cec = i2c_new_dummy(client->adapter, ret); + v4l_info(client, "CEC slave address 0x%02x\n", ret); + + ret = tda1997x_core_init(sd); + if (ret) + goto err_free_mutex; + + /* initialize source pads */ + state->pads[TDA1997X_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&sd->entity, TDA1997X_NUM_PADS, + state->pads); + if (ret) { + v4l_err(client, "failed entity_init: %d", ret); + goto err_free_mutex; + } + + ret = v4l2_async_register_subdev(sd); + if (ret) + goto err_free_media; + + /* register audio DAI */ + if (pdata->audout_format) { + u64 formats; + + if (pdata->audout_width == 32) + formats = SNDRV_PCM_FMTBIT_S32_LE; + else + formats = SNDRV_PCM_FMTBIT_S16_LE; + tda1997x_audio_dai.capture.formats = formats; + ret = snd_soc_register_codec(&state->client->dev, + &tda1997x_codec_driver, + &tda1997x_audio_dai, 1); + if (ret) { + dev_err(&client->dev, "register audio codec failed\n"); + goto err_free_media; + } + dev_set_drvdata(&state->client->dev, state); + v4l_info(state->client, "registered audio codec\n"); + } + + /* request irq */ + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, tda1997x_isr_thread, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + KBUILD_MODNAME, state); + if (ret) { + v4l_err(client, "irq%d reg failed: %d\n", client->irq, ret); + goto err_free_media; + } + + return 0; + +err_free_media: + media_entity_cleanup(&sd->entity); +err_free_mutex: + mutex_destroy(&state->page_lock); + mutex_destroy(&state->lock); +err_free_state: + kfree(state); + dev_err(&client->dev, "%s failed: %d\n", __func__, ret); + + return ret; +} + +static int tda1997x_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct tda1997x_state *state = to_state(sd); + struct tda1997x_platform_data *pdata = &state->pdata; + + if (pdata->audout_format) { + snd_soc_unregister_codec(&client->dev); + mutex_destroy(&state->audio_lock); + } + + disable_irq(state->client->irq); + tda1997x_power_mode(state, 0); + + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + regulator_bulk_disable(TDA1997X_NUM_SUPPLIES, state->supplies); + i2c_unregister_device(state->client_cec); + mutex_destroy(&state->page_lock); + mutex_destroy(&state->lock); + + kfree(state); + + return 0; +} + +static struct i2c_driver tda1997x_i2c_driver = { + .driver = { + .name = "tda1997x", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(tda1997x_of_id), + }, + .probe = tda1997x_probe, + .remove = tda1997x_remove, + .id_table = tda1997x_i2c_id, +}; + +module_i2c_driver(tda1997x_i2c_driver); + +MODULE_AUTHOR("Tim Harvey <tharvey@gateworks.com>"); +MODULE_DESCRIPTION("TDA1997X HDMI Receiver driver"); +MODULE_LICENSE("GPL v2"); diff --git a/include/dt-bindings/media/tda1997x.h b/include/dt-bindings/media/tda1997x.h new file mode 100644 index 0000000..f4cdf87 --- /dev/null +++ b/include/dt-bindings/media/tda1997x.h @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2017 Gateworks Corporation + * + * 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. + */ +#ifndef _DT_BINDINGS_MEDIA_TDA1997X_H +#define _DT_BINDINGS_MEDIA_TDA1997X_H + +/* TDA19973 36bit Video Port control registers */ +#define TDA1997X_VP36_35_32 0 +#define TDA1997X_VP36_31_28 1 +#define TDA1997X_VP36_27_24 2 +#define TDA1997X_VP36_23_20 3 +#define TDA1997X_VP36_19_16 4 +#define TDA1997X_VP36_15_12 5 +#define TDA1997X_VP36_11_08 6 +#define TDA1997X_VP36_07_04 7 +#define TDA1997X_VP36_03_00 8 + +/* TDA19971 24bit Video Port control registers */ +#define TDA1997X_VP24_V23_20 0 +#define TDA1997X_VP24_V19_16 1 +#define TDA1997X_VP24_V15_12 3 +#define TDA1997X_VP24_V11_08 4 +#define TDA1997X_VP24_V07_04 6 +#define TDA1997X_VP24_V03_00 7 + +/* Pin groups */ +#define TDA1997X_VP_OUT_EN 0x80 /* enable output group */ +#define TDA1997X_VP_HIZ 0x40 /* hi-Z output group when not used */ +#define TDA1997X_VP_SWP 0x10 /* pin-swap output group */ +#define TDA1997X_R_CR_CBCR_3_0 (0 | TDA1997X_VP_OUT_EN | TDA1997X_VP_HIZ) +#define TDA1997X_R_CR_CBCR_7_4 (1 | TDA1997X_VP_OUT_EN | TDA1997X_VP_HIZ) +#define TDA1997X_R_CR_CBCR_11_8 (2 | TDA1997X_VP_OUT_EN | TDA1997X_VP_HIZ) +#define TDA1997X_B_CB_3_0 (3 | TDA1997X_VP_OUT_EN | TDA1997X_VP_HIZ) +#define TDA1997X_B_CB_7_4 (4 | TDA1997X_VP_OUT_EN | TDA1997X_VP_HIZ) +#define TDA1997X_B_CB_11_8 (5 | TDA1997X_VP_OUT_EN | TDA1997X_VP_HIZ) +#define TDA1997X_G_Y_3_0 (6 | TDA1997X_VP_OUT_EN | TDA1997X_VP_HIZ) +#define TDA1997X_G_Y_7_4 (7 | TDA1997X_VP_OUT_EN | TDA1997X_VP_HIZ) +#define TDA1997X_G_Y_11_8 (8 | TDA1997X_VP_OUT_EN | TDA1997X_VP_HIZ) +/* pinswapped groups */ +#define TDA1997X_R_CR_CBCR_3_0_S (TDA1997X_R_CR_CBCR_3_0 | TDA1997X_VP_SWAP) +#define TDA1997X_R_CR_CBCR_7_4_S (TDA1997X_R_CR_CBCR_7_4 | TDA1997X_VP_SWAP) +#define TDA1997X_R_CR_CBCR_11_8_S (TDA1997X_R_CR_CBCR_11_8 | TDA1997X_VP_SWAP) +#define TDA1997X_B_CB_3_0_S (TDA1997X_B_CB_3_0 | TDA1997X_VP_SWAP) +#define TDA1997X_B_CB_7_4_S (TDA1997X_B_CB_7_4 | TDA1997X_VP_SWAP) +#define TDA1997X_B_CB_11_8_S (TDA1997X_B_CB_11_8 | TDA1997X_VP_SWAP) +#define TDA1997X_G_Y_3_0_S (TDA1997X_G_Y_3_0 | TDA1997X_VP_SWAP) +#define TDA1997X_G_Y_7_4_S (TDA1997X_G_Y_7_4 | TDA1997X_VP_SWAP) +#define TDA1997X_G_Y_11_8_S (TDA1997X_G_Y_11_8 | TDA1997X_VP_SWAP) + +/* Audio bus DAI format */ +#define TDA1997X_I2S16 1 /* I2S 16bit */ +#define TDA1997X_I2S32 2 /* I2S 32bit */ +#define TDA1997X_SPDIF 3 /* SPDIF */ +#define TDA1997X_OBA 4 /* One Bit Audio */ +#define TDA1997X_DST 5 /* Direct Stream Transfer */ +#define TDA1997X_I2S16_HBR 6 /* HBR straight in I2S 16bit mode */ +#define TDA1997X_I2S16_HBR_DEMUX 7 /* HBR demux in I2S 16bit mode */ +#define TDA1997X_I2S32_HBR_DEMUX 8 /* HBR demux in I2S 32bit mode */ +#define TDA1997X_SPDIF_HBR_DEMUX 9 /* HBR demux in SPDIF mode */ + +/* Audio bus channel layout */ +#define TDA1997X_LAYOUT0 0 /* 2-channel */ +#define TDA1997X_LAYOUT1 1 /* 8-channel */ + +/* Audio bus clock */ +#define TDA1997X_ACLK_16FS 0 +#define TDA1997X_ACLK_32FS 1 +#define TDA1997X_ACLK_64FS 2 +#define TDA1997X_ACLK_128FS 3 +#define TDA1997X_ACLK_256FS 4 +#define TDA1997X_ACLK_512FS 5 + +#endif /* _DT_BINDINGS_MEDIA_TDA1997X_H */ diff --git a/include/media/i2c/tda1997x.h b/include/media/i2c/tda1997x.h new file mode 100644 index 0000000..50cb847 --- /dev/null +++ b/include/media/i2c/tda1997x.h @@ -0,0 +1,53 @@ +/* + * tda1997x - NXP HDMI receiver + * + * Copyright 2017 Tim Harvey <tharvey@gateworks.com> + * + * This program is free software; you may redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +#ifndef _TDA1997X_ +#define _TDA1997X_ + +/* Platform Data */ +struct tda1997x_platform_data { + u32 vidout_bus_width; + u8 vidout_port_cfg[9]; + /* pin polarity (1=invert) */ + bool vidout_inv_de; + bool vidout_inv_hs; + bool vidout_inv_vs; + bool vidout_inv_pclk; + /* clock delays (0=-8, 1=-7 ... 15=+7 pixels) */ + u8 vidout_delay_hs; + u8 vidout_delay_vs; + u8 vidout_delay_de; + u8 vidout_delay_pclk; + /* sync selections (controls how sync pins are derived) */ + u8 vidout_sel_hs; + u8 vidout_sel_vs; + u8 vidout_sel_de; + + /* Audio Port Output */ + int audout_format; + u32 audout_mclk_fs; /* clock multiplier */ + u32 audout_width; /* 13 or 32 bit */ + u32 audout_layout; /* layout0=AP0 layout1=AP0,AP1,AP2,AP3 */ + bool audout_layoutauto; /* audio layout dictated by pkt header */ + bool audout_invert_clk; /* data valid on rising edge of BCLK */ + bool audio_auto_mute; /* enable hardware audio auto-mute */ +}; + +#endif
Add support for the TDA1997x HDMI receivers. Cc: Hans Verkuil <hverkuil@xs4all.nl> Signed-off-by: Tim Harvey <tharvey@gateworks.com> --- v2: - implement dv timings enum/cap - remove deprecated g_mbus_config op - fix dv_query_timings - add EDID get/set handling - remove max-pixel-rate support - add audio codec DAI support - use new audio bindings --- drivers/media/i2c/Kconfig | 9 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/tda1997x.c | 3336 ++++++++++++++++++++++++++++++++++ include/dt-bindings/media/tda1997x.h | 78 + include/media/i2c/tda1997x.h | 53 + 5 files changed, 3477 insertions(+) create mode 100644 drivers/media/i2c/tda1997x.c create mode 100644 include/dt-bindings/media/tda1997x.h create mode 100644 include/media/i2c/tda1997x.h