mirror of
https://github.com/torvalds/linux.git
synced 2024-12-15 15:41:58 +00:00
d272bc92c4
The use of 'DTV' is very confusing since it normally refers to Digital TV e.g. DVB etc. Instead use 'DV' (Digital Video), which nicely corresponds to the DV Timings API used to configure such receivers and transmitters. We keep an alias to avoid breaking userspace applications. Since this alias is only available if __KERNEL__ is *not* defined (i.e. it is only available for userspace, not kernelspace), any drivers that use it also have to be converted to the new define. These drivers are adv7604, adv7842 and tda1997x. Signed-off-by: Hans Verkuil <hansverk@cisco.com> Signed-off-by: Mauro Carvalho Chehab <mchehab+samsung@kernel.org>
2822 lines
74 KiB
C
2822 lines
74 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (C) 2018 Gateworks Corporation
|
|
*/
|
|
#include <linux/delay.h>
|
|
#include <linux/hdmi.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/init.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of_graph.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/regulator/consumer.h>
|
|
#include <linux/types.h>
|
|
#include <linux/v4l2-dv-timings.h>
|
|
#include <linux/videodev2.h>
|
|
|
|
#include <media/v4l2-ctrls.h>
|
|
#include <media/v4l2-device.h>
|
|
#include <media/v4l2-dv-timings.h>
|
|
#include <media/v4l2-event.h>
|
|
#include <media/v4l2-fwnode.h>
|
|
#include <media/i2c/tda1997x.h>
|
|
|
|
#include <sound/core.h>
|
|
#include <sound/pcm.h>
|
|
#include <sound/pcm_params.h>
|
|
#include <sound/soc.h>
|
|
|
|
#include <dt-bindings/media/tda1997x.h>
|
|
|
|
#include "tda1997x_regs.h"
|
|
|
|
#define TDA1997X_MBUS_CODES 5
|
|
|
|
/* debug level */
|
|
static int debug;
|
|
module_param(debug, int, 0644);
|
|
MODULE_PARM_DESC(debug, "debug level (0-2)");
|
|
|
|
/* Audio formats */
|
|
static const char * const audtype_names[] = {
|
|
"PCM", /* PCM Samples */
|
|
"HBR", /* High Bit Rate Audio */
|
|
"OBA", /* One-Bit Audio */
|
|
"DST" /* Direct Stream Transfer */
|
|
};
|
|
|
|
/* Audio output port formats */
|
|
enum audfmt_types {
|
|
AUDFMT_TYPE_DISABLED = 0,
|
|
AUDFMT_TYPE_I2S,
|
|
AUDFMT_TYPE_SPDIF,
|
|
};
|
|
static const char * const audfmt_names[] = {
|
|
"Disabled",
|
|
"I2S",
|
|
"SPDIF",
|
|
};
|
|
|
|
/* Video input formats */
|
|
static const char * const hdmi_colorspace_names[] = {
|
|
"RGB", "YUV422", "YUV444", "YUV420", "", "", "", "",
|
|
};
|
|
static const char * const hdmi_colorimetry_names[] = {
|
|
"", "ITU601", "ITU709", "Extended",
|
|
};
|
|
static const char * const v4l2_quantization_names[] = {
|
|
"Default",
|
|
"Full Range (0-255)",
|
|
"Limited Range (16-235)",
|
|
};
|
|
|
|
/* Video output port formats */
|
|
static const char * const vidfmt_names[] = {
|
|
"RGB444/YUV444", /* RGB/YUV444 16bit data bus, 8bpp */
|
|
"YUV422 semi-planar", /* YUV422 16bit data base, 8bpp */
|
|
"YUV422 CCIR656", /* BT656 (YUV 8bpp 2 clock per pixel) */
|
|
"Invalid",
|
|
};
|
|
|
|
/*
|
|
* Colorspace conversion matrices
|
|
*/
|
|
struct color_matrix_coefs {
|
|
const char *name;
|
|
/* Input offsets */
|
|
s16 offint1;
|
|
s16 offint2;
|
|
s16 offint3;
|
|
/* Coeficients */
|
|
s16 p11coef;
|
|
s16 p12coef;
|
|
s16 p13coef;
|
|
s16 p21coef;
|
|
s16 p22coef;
|
|
s16 p23coef;
|
|
s16 p31coef;
|
|
s16 p32coef;
|
|
s16 p33coef;
|
|
/* Output offsets */
|
|
s16 offout1;
|
|
s16 offout2;
|
|
s16 offout3;
|
|
};
|
|
|
|
enum {
|
|
ITU709_RGBFULL,
|
|
ITU601_RGBFULL,
|
|
RGBLIMITED_RGBFULL,
|
|
RGBLIMITED_ITU601,
|
|
RGBLIMITED_ITU709,
|
|
RGBFULL_ITU601,
|
|
RGBFULL_ITU709,
|
|
};
|
|
|
|
/* NB: 4096 is 1.0 using fixed point numbers */
|
|
static const struct color_matrix_coefs conv_matrix[] = {
|
|
{
|
|
"YUV709 -> RGB full",
|
|
-256, -2048, -2048,
|
|
4769, -2183, -873,
|
|
4769, 7343, 0,
|
|
4769, 0, 8652,
|
|
0, 0, 0,
|
|
},
|
|
{
|
|
"YUV601 -> RGB full",
|
|
-256, -2048, -2048,
|
|
4769, -3330, -1602,
|
|
4769, 6538, 0,
|
|
4769, 0, 8264,
|
|
256, 256, 256,
|
|
},
|
|
{
|
|
"RGB limited -> RGB full",
|
|
-256, -256, -256,
|
|
0, 4769, 0,
|
|
0, 0, 4769,
|
|
4769, 0, 0,
|
|
0, 0, 0,
|
|
},
|
|
{
|
|
"RGB limited -> ITU601",
|
|
-256, -256, -256,
|
|
2404, 1225, 467,
|
|
-1754, 2095, -341,
|
|
-1388, -707, 2095,
|
|
256, 2048, 2048,
|
|
},
|
|
{
|
|
"RGB limited -> ITU709",
|
|
-256, -256, -256,
|
|
2918, 867, 295,
|
|
-1894, 2087, -190,
|
|
-1607, -477, 2087,
|
|
256, 2048, 2048,
|
|
},
|
|
{
|
|
"RGB full -> ITU601",
|
|
0, 0, 0,
|
|
2065, 1052, 401,
|
|
-1506, 1799, -293,
|
|
-1192, -607, 1799,
|
|
256, 2048, 2048,
|
|
},
|
|
{
|
|
"RGB full -> ITU709",
|
|
0, 0, 0,
|
|
2506, 745, 253,
|
|
-1627, 1792, -163,
|
|
-1380, -410, 1792,
|
|
256, 2048, 2048,
|
|
},
|
|
};
|
|
|
|
static const struct v4l2_dv_timings_cap tda1997x_dv_timings_cap = {
|
|
.type = V4L2_DV_BT_656_1120,
|
|
/* keep this initialization for compatibility with GCC < 4.4.6 */
|
|
.reserved = { 0 },
|
|
|
|
V4L2_INIT_BT_TIMINGS(
|
|
640, 1920, /* min/max width */
|
|
350, 1200, /* min/max height */
|
|
13000000, 165000000, /* min/max pixelclock */
|
|
/* standards */
|
|
V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT |
|
|
V4L2_DV_BT_STD_GTF | V4L2_DV_BT_STD_CVT,
|
|
/* capabilities */
|
|
V4L2_DV_BT_CAP_INTERLACED | V4L2_DV_BT_CAP_PROGRESSIVE |
|
|
V4L2_DV_BT_CAP_REDUCED_BLANKING |
|
|
V4L2_DV_BT_CAP_CUSTOM
|
|
)
|
|
};
|
|
|
|
/* regulator supplies */
|
|
static const char * const tda1997x_supply_name[] = {
|
|
"DOVDD", /* Digital I/O supply */
|
|
"DVDD", /* Digital Core supply */
|
|
"AVDD", /* Analog supply */
|
|
};
|
|
|
|
#define TDA1997X_NUM_SUPPLIES ARRAY_SIZE(tda1997x_supply_name)
|
|
|
|
enum tda1997x_type {
|
|
TDA19971,
|
|
TDA19973,
|
|
};
|
|
|
|
enum tda1997x_hdmi_pads {
|
|
TDA1997X_PAD_SOURCE,
|
|
TDA1997X_NUM_PADS,
|
|
};
|
|
|
|
struct tda1997x_chip_info {
|
|
enum tda1997x_type type;
|
|
const char *name;
|
|
};
|
|
|
|
struct tda1997x_state {
|
|
const struct tda1997x_chip_info *info;
|
|
struct tda1997x_platform_data pdata;
|
|
struct i2c_client *client;
|
|
struct i2c_client *client_cec;
|
|
struct v4l2_subdev sd;
|
|
struct regulator_bulk_data supplies[TDA1997X_NUM_SUPPLIES];
|
|
struct media_pad pads[TDA1997X_NUM_PADS];
|
|
struct mutex lock;
|
|
struct mutex page_lock;
|
|
char page;
|
|
|
|
/* detected info from chip */
|
|
int chip_revision;
|
|
char port_30bit;
|
|
char output_2p5;
|
|
char tmdsb_clk;
|
|
char tmdsb_soc;
|
|
|
|
/* status info */
|
|
char hdmi_status;
|
|
char mptrw_in_progress;
|
|
char activity_status;
|
|
char input_detect[2];
|
|
|
|
/* video */
|
|
struct hdmi_avi_infoframe avi_infoframe;
|
|
struct v4l2_hdmi_colorimetry colorimetry;
|
|
u32 rgb_quantization_range;
|
|
struct v4l2_dv_timings timings;
|
|
int fps;
|
|
const struct color_matrix_coefs *conv;
|
|
u32 mbus_codes[TDA1997X_MBUS_CODES]; /* available modes */
|
|
u32 mbus_code; /* current mode */
|
|
u8 vid_fmt;
|
|
|
|
/* controls */
|
|
struct v4l2_ctrl_handler hdl;
|
|
struct v4l2_ctrl *detect_tx_5v_ctrl;
|
|
struct v4l2_ctrl *rgb_quantization_range_ctrl;
|
|
|
|
/* audio */
|
|
u8 audio_ch_alloc;
|
|
int audio_samplerate;
|
|
int audio_channels;
|
|
int audio_samplesize;
|
|
int audio_type;
|
|
struct mutex audio_lock;
|
|
struct snd_pcm_substream *audio_stream;
|
|
|
|
/* EDID */
|
|
struct {
|
|
u8 edid[256];
|
|
u32 present;
|
|
unsigned int blocks;
|
|
} edid;
|
|
struct delayed_work delayed_work_enable_hpd;
|
|
};
|
|
|
|
static const struct v4l2_event tda1997x_ev_fmt = {
|
|
.type = V4L2_EVENT_SOURCE_CHANGE,
|
|
.u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION,
|
|
};
|
|
|
|
static const struct tda1997x_chip_info tda1997x_chip_info[] = {
|
|
[TDA19971] = {
|
|
.type = TDA19971,
|
|
.name = "tda19971",
|
|
},
|
|
[TDA19973] = {
|
|
.type = TDA19973,
|
|
.name = "tda19973",
|
|
},
|
|
};
|
|
|
|
static inline struct tda1997x_state *to_state(struct v4l2_subdev *sd)
|
|
{
|
|
return container_of(sd, struct tda1997x_state, sd);
|
|
}
|
|
|
|
static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl)
|
|
{
|
|
return &container_of(ctrl->handler, struct tda1997x_state, hdl)->sd;
|
|
}
|
|
|
|
static int tda1997x_cec_read(struct v4l2_subdev *sd, u8 reg)
|
|
{
|
|
struct tda1997x_state *state = to_state(sd);
|
|
int val;
|
|
|
|
val = i2c_smbus_read_byte_data(state->client_cec, reg);
|
|
if (val < 0) {
|
|
v4l_err(state->client, "read reg error: reg=%2x\n", reg);
|
|
val = -1;
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
static int tda1997x_cec_write(struct v4l2_subdev *sd, u8 reg, u8 val)
|
|
{
|
|
struct tda1997x_state *state = to_state(sd);
|
|
int ret = 0;
|
|
|
|
ret = i2c_smbus_write_byte_data(state->client_cec, reg, val);
|
|
if (ret < 0) {
|
|
v4l_err(state->client, "write reg error:reg=%2x,val=%2x\n",
|
|
reg, val);
|
|
ret = -1;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* I2C transfer
|
|
*/
|
|
|
|
static int tda1997x_setpage(struct v4l2_subdev *sd, u8 page)
|
|
{
|
|
struct tda1997x_state *state = to_state(sd);
|
|
int ret;
|
|
|
|
if (state->page != page) {
|
|
ret = i2c_smbus_write_byte_data(state->client,
|
|
REG_CURPAGE_00H, page);
|
|
if (ret < 0) {
|
|
v4l_err(state->client,
|
|
"write reg error:reg=%2x,val=%2x\n",
|
|
REG_CURPAGE_00H, page);
|
|
return ret;
|
|
}
|
|
state->page = page;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static inline int io_read(struct v4l2_subdev *sd, u16 reg)
|
|
{
|
|
struct tda1997x_state *state = to_state(sd);
|
|
int val;
|
|
|
|
mutex_lock(&state->page_lock);
|
|
if (tda1997x_setpage(sd, reg >> 8)) {
|
|
val = -1;
|
|
goto out;
|
|
}
|
|
|
|
val = i2c_smbus_read_byte_data(state->client, reg&0xff);
|
|
if (val < 0) {
|
|
v4l_err(state->client, "read reg error: reg=%2x\n", reg & 0xff);
|
|
val = -1;
|
|
goto out;
|
|
}
|
|
|
|
out:
|
|
mutex_unlock(&state->page_lock);
|
|
return val;
|
|
}
|
|
|
|
static inline long io_read16(struct v4l2_subdev *sd, u16 reg)
|
|
{
|
|
int val;
|
|
long lval = 0;
|
|
|
|
val = io_read(sd, reg);
|
|
if (val < 0)
|
|
return val;
|
|
lval |= (val << 8);
|
|
val = io_read(sd, reg + 1);
|
|
if (val < 0)
|
|
return val;
|
|
lval |= val;
|
|
|
|
return lval;
|
|
}
|
|
|
|
static inline long io_read24(struct v4l2_subdev *sd, u16 reg)
|
|
{
|
|
int val;
|
|
long lval = 0;
|
|
|
|
val = io_read(sd, reg);
|
|
if (val < 0)
|
|
return val;
|
|
lval |= (val << 16);
|
|
val = io_read(sd, reg + 1);
|
|
if (val < 0)
|
|
return val;
|
|
lval |= (val << 8);
|
|
val = io_read(sd, reg + 2);
|
|
if (val < 0)
|
|
return val;
|
|
lval |= val;
|
|
|
|
return lval;
|
|
}
|
|
|
|
static unsigned int io_readn(struct v4l2_subdev *sd, u16 reg, u8 len, u8 *data)
|
|
{
|
|
int i;
|
|
int sz = 0;
|
|
int val;
|
|
|
|
for (i = 0; i < len; i++) {
|
|
val = io_read(sd, reg + i);
|
|
if (val < 0)
|
|
break;
|
|
data[i] = val;
|
|
sz++;
|
|
}
|
|
|
|
return sz;
|
|
}
|
|
|
|
static int io_write(struct v4l2_subdev *sd, u16 reg, u8 val)
|
|
{
|
|
struct tda1997x_state *state = to_state(sd);
|
|
s32 ret = 0;
|
|
|
|
mutex_lock(&state->page_lock);
|
|
if (tda1997x_setpage(sd, reg >> 8)) {
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
ret = i2c_smbus_write_byte_data(state->client, reg & 0xff, val);
|
|
if (ret < 0) {
|
|
v4l_err(state->client, "write reg error:reg=%2x,val=%2x\n",
|
|
reg&0xff, val);
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
out:
|
|
mutex_unlock(&state->page_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int io_write16(struct v4l2_subdev *sd, u16 reg, u16 val)
|
|
{
|
|
int ret;
|
|
|
|
ret = io_write(sd, reg, (val >> 8) & 0xff);
|
|
if (ret < 0)
|
|
return ret;
|
|
ret = io_write(sd, reg + 1, val & 0xff);
|
|
if (ret < 0)
|
|
return ret;
|
|
return 0;
|
|
}
|
|
|
|
static int io_write24(struct v4l2_subdev *sd, u16 reg, u32 val)
|
|
{
|
|
int ret;
|
|
|
|
ret = io_write(sd, reg, (val >> 16) & 0xff);
|
|
if (ret < 0)
|
|
return ret;
|
|
ret = io_write(sd, reg + 1, (val >> 8) & 0xff);
|
|
if (ret < 0)
|
|
return ret;
|
|
ret = io_write(sd, reg + 2, val & 0xff);
|
|
if (ret < 0)
|
|
return ret;
|
|
return 0;
|
|
}
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* Hotplug
|
|
*/
|
|
|
|
enum hpd_mode {
|
|
HPD_LOW_BP, /* HPD low and pulse of at least 100ms */
|
|
HPD_LOW_OTHER, /* HPD low and pulse of at least 100ms */
|
|
HPD_HIGH_BP, /* HIGH */
|
|
HPD_HIGH_OTHER,
|
|
HPD_PULSE, /* HPD low pulse */
|
|
};
|
|
|
|
/* manual HPD (Hot Plug Detect) control */
|
|
static int tda1997x_manual_hpd(struct v4l2_subdev *sd, enum hpd_mode mode)
|
|
{
|
|
u8 hpd_auto, hpd_pwr, hpd_man;
|
|
|
|
hpd_auto = io_read(sd, REG_HPD_AUTO_CTRL);
|
|
hpd_pwr = io_read(sd, REG_HPD_POWER);
|
|
hpd_man = io_read(sd, REG_HPD_MAN_CTRL);
|
|
|
|
/* mask out unused bits */
|
|
hpd_man &= (HPD_MAN_CTRL_HPD_PULSE |
|
|
HPD_MAN_CTRL_5VEN |
|
|
HPD_MAN_CTRL_HPD_B |
|
|
HPD_MAN_CTRL_HPD_A);
|
|
|
|
switch (mode) {
|
|
/* HPD low and pulse of at least 100ms */
|
|
case HPD_LOW_BP:
|
|
/* hpd_bp=0 */
|
|
hpd_pwr &= ~HPD_POWER_BP_MASK;
|
|
/* disable HPD_A and HPD_B */
|
|
hpd_man &= ~(HPD_MAN_CTRL_HPD_A | HPD_MAN_CTRL_HPD_B);
|
|
io_write(sd, REG_HPD_POWER, hpd_pwr);
|
|
io_write(sd, REG_HPD_MAN_CTRL, hpd_man);
|
|
break;
|
|
/* HPD high */
|
|
case HPD_HIGH_BP:
|
|
/* hpd_bp=1 */
|
|
hpd_pwr &= ~HPD_POWER_BP_MASK;
|
|
hpd_pwr |= 1 << HPD_POWER_BP_SHIFT;
|
|
io_write(sd, REG_HPD_POWER, hpd_pwr);
|
|
break;
|
|
/* HPD low and pulse of at least 100ms */
|
|
case HPD_LOW_OTHER:
|
|
/* disable HPD_A and HPD_B */
|
|
hpd_man &= ~(HPD_MAN_CTRL_HPD_A | HPD_MAN_CTRL_HPD_B);
|
|
/* hp_other=0 */
|
|
hpd_auto &= ~HPD_AUTO_HP_OTHER;
|
|
io_write(sd, REG_HPD_AUTO_CTRL, hpd_auto);
|
|
io_write(sd, REG_HPD_MAN_CTRL, hpd_man);
|
|
break;
|
|
/* HPD high */
|
|
case HPD_HIGH_OTHER:
|
|
hpd_auto |= HPD_AUTO_HP_OTHER;
|
|
io_write(sd, REG_HPD_AUTO_CTRL, hpd_auto);
|
|
break;
|
|
/* HPD low pulse */
|
|
case HPD_PULSE:
|
|
/* disable HPD_A and HPD_B */
|
|
hpd_man &= ~(HPD_MAN_CTRL_HPD_A | HPD_MAN_CTRL_HPD_B);
|
|
io_write(sd, REG_HPD_MAN_CTRL, hpd_man);
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void tda1997x_delayed_work_enable_hpd(struct work_struct *work)
|
|
{
|
|
struct delayed_work *dwork = to_delayed_work(work);
|
|
struct tda1997x_state *state = container_of(dwork,
|
|
struct tda1997x_state,
|
|
delayed_work_enable_hpd);
|
|
struct v4l2_subdev *sd = &state->sd;
|
|
|
|
v4l2_dbg(2, debug, sd, "%s:\n", __func__);
|
|
|
|
/* Set HPD high */
|
|
tda1997x_manual_hpd(sd, HPD_HIGH_OTHER);
|
|
tda1997x_manual_hpd(sd, HPD_HIGH_BP);
|
|
|
|
state->edid.present = 1;
|
|
}
|
|
|
|
static void tda1997x_disable_edid(struct v4l2_subdev *sd)
|
|
{
|
|
struct tda1997x_state *state = to_state(sd);
|
|
|
|
v4l2_dbg(1, debug, sd, "%s\n", __func__);
|
|
cancel_delayed_work_sync(&state->delayed_work_enable_hpd);
|
|
|
|
/* Set HPD low */
|
|
tda1997x_manual_hpd(sd, HPD_LOW_BP);
|
|
}
|
|
|
|
static void tda1997x_enable_edid(struct v4l2_subdev *sd)
|
|
{
|
|
struct tda1997x_state *state = to_state(sd);
|
|
|
|
v4l2_dbg(1, debug, sd, "%s\n", __func__);
|
|
|
|
/* Enable hotplug after 100ms */
|
|
schedule_delayed_work(&state->delayed_work_enable_hpd, HZ / 10);
|
|
}
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* Signal Control
|
|
*/
|
|
|
|
/*
|
|
* configure vid_fmt based on mbus_code
|
|
*/
|
|
static int
|
|
tda1997x_setup_format(struct tda1997x_state *state, u32 code)
|
|
{
|
|
v4l_dbg(1, debug, state->client, "%s code=0x%x\n", __func__, code);
|
|
switch (code) {
|
|
case MEDIA_BUS_FMT_RGB121212_1X36:
|
|
case MEDIA_BUS_FMT_RGB888_1X24:
|
|
case MEDIA_BUS_FMT_YUV12_1X36:
|
|
case MEDIA_BUS_FMT_YUV8_1X24:
|
|
state->vid_fmt = OF_FMT_444;
|
|
break;
|
|
case MEDIA_BUS_FMT_UYVY12_1X24:
|
|
case MEDIA_BUS_FMT_UYVY10_1X20:
|
|
case MEDIA_BUS_FMT_UYVY8_1X16:
|
|
state->vid_fmt = OF_FMT_422_SMPT;
|
|
break;
|
|
case MEDIA_BUS_FMT_UYVY12_2X12:
|
|
case MEDIA_BUS_FMT_UYVY10_2X10:
|
|
case MEDIA_BUS_FMT_UYVY8_2X8:
|
|
state->vid_fmt = OF_FMT_422_CCIR;
|
|
break;
|
|
default:
|
|
v4l_err(state->client, "incompatible format (0x%x)\n", code);
|
|
return -EINVAL;
|
|
}
|
|
v4l_dbg(1, debug, state->client, "%s code=0x%x fmt=%s\n", __func__,
|
|
code, vidfmt_names[state->vid_fmt]);
|
|
state->mbus_code = code;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* The color conversion matrix will convert between the colorimetry of the
|
|
* HDMI input to the desired output format RGB|YUV. RGB output is to be
|
|
* full-range and YUV is to be limited range.
|
|
*
|
|
* RGB full-range uses values from 0 to 255 which is recommended on a monitor
|
|
* and RGB Limited uses values from 16 to 236 (16=black, 235=white) which is
|
|
* typically recommended on a TV.
|
|
*/
|
|
static void
|
|
tda1997x_configure_csc(struct v4l2_subdev *sd)
|
|
{
|
|
struct tda1997x_state *state = to_state(sd);
|
|
struct hdmi_avi_infoframe *avi = &state->avi_infoframe;
|
|
struct v4l2_hdmi_colorimetry *c = &state->colorimetry;
|
|
/* Blanking code values depend on output colorspace (RGB or YUV) */
|
|
struct blanking_codes {
|
|
s16 code_gy;
|
|
s16 code_bu;
|
|
s16 code_rv;
|
|
};
|
|
static const struct blanking_codes rgb_blanking = { 64, 64, 64 };
|
|
static const struct blanking_codes yuv_blanking = { 64, 512, 512 };
|
|
const struct blanking_codes *blanking_codes = NULL;
|
|
u8 reg;
|
|
|
|
v4l_dbg(1, debug, state->client, "input:%s quant:%s output:%s\n",
|
|
hdmi_colorspace_names[avi->colorspace],
|
|
v4l2_quantization_names[c->quantization],
|
|
vidfmt_names[state->vid_fmt]);
|
|
state->conv = NULL;
|
|
switch (state->vid_fmt) {
|
|
/* RGB output */
|
|
case OF_FMT_444:
|
|
blanking_codes = &rgb_blanking;
|
|
if (c->colorspace == V4L2_COLORSPACE_SRGB) {
|
|
if (c->quantization == V4L2_QUANTIZATION_LIM_RANGE)
|
|
state->conv = &conv_matrix[RGBLIMITED_RGBFULL];
|
|
} else {
|
|
if (c->colorspace == V4L2_COLORSPACE_REC709)
|
|
state->conv = &conv_matrix[ITU709_RGBFULL];
|
|
else if (c->colorspace == V4L2_COLORSPACE_SMPTE170M)
|
|
state->conv = &conv_matrix[ITU601_RGBFULL];
|
|
}
|
|
break;
|
|
|
|
/* YUV output */
|
|
case OF_FMT_422_SMPT: /* semi-planar */
|
|
case OF_FMT_422_CCIR: /* CCIR656 */
|
|
blanking_codes = &yuv_blanking;
|
|
if ((c->colorspace == V4L2_COLORSPACE_SRGB) &&
|
|
(c->quantization == V4L2_QUANTIZATION_FULL_RANGE)) {
|
|
if (state->timings.bt.height <= 576)
|
|
state->conv = &conv_matrix[RGBFULL_ITU601];
|
|
else
|
|
state->conv = &conv_matrix[RGBFULL_ITU709];
|
|
} else if ((c->colorspace == V4L2_COLORSPACE_SRGB) &&
|
|
(c->quantization == V4L2_QUANTIZATION_LIM_RANGE)) {
|
|
if (state->timings.bt.height <= 576)
|
|
state->conv = &conv_matrix[RGBLIMITED_ITU601];
|
|
else
|
|
state->conv = &conv_matrix[RGBLIMITED_ITU709];
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (state->conv) {
|
|
v4l_dbg(1, debug, state->client, "%s\n",
|
|
state->conv->name);
|
|
/* enable matrix conversion */
|
|
reg = io_read(sd, REG_VDP_CTRL);
|
|
reg &= ~VDP_CTRL_MATRIX_BP;
|
|
io_write(sd, REG_VDP_CTRL, reg);
|
|
/* offset inputs */
|
|
io_write16(sd, REG_VDP_MATRIX + 0, state->conv->offint1);
|
|
io_write16(sd, REG_VDP_MATRIX + 2, state->conv->offint2);
|
|
io_write16(sd, REG_VDP_MATRIX + 4, state->conv->offint3);
|
|
/* coefficients */
|
|
io_write16(sd, REG_VDP_MATRIX + 6, state->conv->p11coef);
|
|
io_write16(sd, REG_VDP_MATRIX + 8, state->conv->p12coef);
|
|
io_write16(sd, REG_VDP_MATRIX + 10, state->conv->p13coef);
|
|
io_write16(sd, REG_VDP_MATRIX + 12, state->conv->p21coef);
|
|
io_write16(sd, REG_VDP_MATRIX + 14, state->conv->p22coef);
|
|
io_write16(sd, REG_VDP_MATRIX + 16, state->conv->p23coef);
|
|
io_write16(sd, REG_VDP_MATRIX + 18, state->conv->p31coef);
|
|
io_write16(sd, REG_VDP_MATRIX + 20, state->conv->p32coef);
|
|
io_write16(sd, REG_VDP_MATRIX + 22, state->conv->p33coef);
|
|
/* offset outputs */
|
|
io_write16(sd, REG_VDP_MATRIX + 24, state->conv->offout1);
|
|
io_write16(sd, REG_VDP_MATRIX + 26, state->conv->offout2);
|
|
io_write16(sd, REG_VDP_MATRIX + 28, state->conv->offout3);
|
|
} else {
|
|
/* disable matrix conversion */
|
|
reg = io_read(sd, REG_VDP_CTRL);
|
|
reg |= VDP_CTRL_MATRIX_BP;
|
|
io_write(sd, REG_VDP_CTRL, reg);
|
|
}
|
|
|
|
/* SetBlankingCodes */
|
|
if (blanking_codes) {
|
|
io_write16(sd, REG_BLK_GY, blanking_codes->code_gy);
|
|
io_write16(sd, REG_BLK_BU, blanking_codes->code_bu);
|
|
io_write16(sd, REG_BLK_RV, blanking_codes->code_rv);
|
|
}
|
|
}
|
|
|
|
/* Configure frame detection window and VHREF timing generator */
|
|
static void
|
|
tda1997x_configure_vhref(struct v4l2_subdev *sd)
|
|
{
|
|
struct tda1997x_state *state = to_state(sd);
|
|
const struct v4l2_bt_timings *bt = &state->timings.bt;
|
|
int width, lines;
|
|
u16 href_start, href_end;
|
|
u16 vref_f1_start, vref_f2_start;
|
|
u8 vref_f1_width, vref_f2_width;
|
|
u8 field_polarity;
|
|
u16 fieldref_f1_start, fieldref_f2_start;
|
|
u8 reg;
|
|
|
|
href_start = bt->hbackporch + bt->hsync + 1;
|
|
href_end = href_start + bt->width;
|
|
vref_f1_start = bt->height + bt->vbackporch + bt->vsync +
|
|
bt->il_vbackporch + bt->il_vsync +
|
|
bt->il_vfrontporch;
|
|
vref_f1_width = bt->vbackporch + bt->vsync + bt->vfrontporch;
|
|
vref_f2_start = 0;
|
|
vref_f2_width = 0;
|
|
fieldref_f1_start = 0;
|
|
fieldref_f2_start = 0;
|
|
if (bt->interlaced) {
|
|
vref_f2_start = (bt->height / 2) +
|
|
(bt->il_vbackporch + bt->il_vsync - 1);
|
|
vref_f2_width = bt->il_vbackporch + bt->il_vsync +
|
|
bt->il_vfrontporch;
|
|
fieldref_f2_start = vref_f2_start + bt->il_vfrontporch +
|
|
fieldref_f1_start;
|
|
}
|
|
field_polarity = 0;
|
|
|
|
width = V4L2_DV_BT_FRAME_WIDTH(bt);
|
|
lines = V4L2_DV_BT_FRAME_HEIGHT(bt);
|
|
|
|
/*
|
|
* Configure Frame Detection Window:
|
|
* horiz area where the VHREF module consider a VSYNC a new frame
|
|
*/
|
|
io_write16(sd, REG_FDW_S, 0x2ef); /* start position */
|
|
io_write16(sd, REG_FDW_E, 0x141); /* end position */
|
|
|
|
/* Set Pixel And Line Counters */
|
|
if (state->chip_revision == 0)
|
|
io_write16(sd, REG_PXCNT_PR, 4);
|
|
else
|
|
io_write16(sd, REG_PXCNT_PR, 1);
|
|
io_write16(sd, REG_PXCNT_NPIX, width & MASK_VHREF);
|
|
io_write16(sd, REG_LCNT_PR, 1);
|
|
io_write16(sd, REG_LCNT_NLIN, lines & MASK_VHREF);
|
|
|
|
/*
|
|
* Configure the VHRef timing generator responsible for rebuilding all
|
|
* horiz and vert synch and ref signals from its input allowing auto
|
|
* detection algorithms and forcing predefined modes (480i & 576i)
|
|
*/
|
|
reg = VHREF_STD_DET_OFF << VHREF_STD_DET_SHIFT;
|
|
io_write(sd, REG_VHREF_CTRL, reg);
|
|
|
|
/*
|
|
* Configure the VHRef timing values. In case the VHREF generator has
|
|
* been configured in manual mode, this will allow to manually set all
|
|
* horiz and vert ref values (non-active pixel areas) of the generator
|
|
* and allows setting the frame reference params.
|
|
*/
|
|
/* horizontal reference start/end */
|
|
io_write16(sd, REG_HREF_S, href_start & MASK_VHREF);
|
|
io_write16(sd, REG_HREF_E, href_end & MASK_VHREF);
|
|
/* vertical reference f1 start/end */
|
|
io_write16(sd, REG_VREF_F1_S, vref_f1_start & MASK_VHREF);
|
|
io_write(sd, REG_VREF_F1_WIDTH, vref_f1_width);
|
|
/* vertical reference f2 start/end */
|
|
io_write16(sd, REG_VREF_F2_S, vref_f2_start & MASK_VHREF);
|
|
io_write(sd, REG_VREF_F2_WIDTH, vref_f2_width);
|
|
|
|
/* F1/F2 FREF, field polarity */
|
|
reg = fieldref_f1_start & MASK_VHREF;
|
|
reg |= field_polarity << 8;
|
|
io_write16(sd, REG_FREF_F1_S, reg);
|
|
reg = fieldref_f2_start & MASK_VHREF;
|
|
io_write16(sd, REG_FREF_F2_S, reg);
|
|
}
|
|
|
|
/* Configure Video Output port signals */
|
|
static int
|
|
tda1997x_configure_vidout(struct tda1997x_state *state)
|
|
{
|
|
struct v4l2_subdev *sd = &state->sd;
|
|
struct tda1997x_platform_data *pdata = &state->pdata;
|
|
u8 prefilter;
|
|
u8 reg;
|
|
|
|
/* Configure pixel clock generator: delay, polarity, rate */
|
|
reg = (state->vid_fmt == OF_FMT_422_CCIR) ?
|
|
PCLK_SEL_X2 : PCLK_SEL_X1;
|
|
reg |= pdata->vidout_delay_pclk << PCLK_DELAY_SHIFT;
|
|
reg |= pdata->vidout_inv_pclk << PCLK_INV_SHIFT;
|
|
io_write(sd, REG_PCLK, reg);
|
|
|
|
/* Configure pre-filter */
|
|
prefilter = 0; /* filters off */
|
|
/* YUV422 mode requires conversion */
|
|
if ((state->vid_fmt == OF_FMT_422_SMPT) ||
|
|
(state->vid_fmt == OF_FMT_422_CCIR)) {
|
|
/* 2/7 taps for Rv and Bu */
|
|
prefilter = FILTERS_CTRL_2_7TAP << FILTERS_CTRL_BU_SHIFT |
|
|
FILTERS_CTRL_2_7TAP << FILTERS_CTRL_RV_SHIFT;
|
|
}
|
|
io_write(sd, REG_FILTERS_CTRL, prefilter);
|
|
|
|
/* Configure video port */
|
|
reg = state->vid_fmt & OF_FMT_MASK;
|
|
if (state->vid_fmt == OF_FMT_422_CCIR)
|
|
reg |= (OF_BLK | OF_TRC);
|
|
reg |= OF_VP_ENABLE;
|
|
io_write(sd, REG_OF, reg);
|
|
|
|
/* Configure formatter and conversions */
|
|
reg = io_read(sd, REG_VDP_CTRL);
|
|
/* pre-filter is needed unless (REG_FILTERS_CTRL == 0) */
|
|
if (!prefilter)
|
|
reg |= VDP_CTRL_PREFILTER_BP;
|
|
else
|
|
reg &= ~VDP_CTRL_PREFILTER_BP;
|
|
/* formatter is needed for YUV422 and for trc/blc codes */
|
|
if (state->vid_fmt == OF_FMT_444)
|
|
reg |= VDP_CTRL_FORMATTER_BP;
|
|
/* formatter and compdel needed for timing/blanking codes */
|
|
else
|
|
reg &= ~(VDP_CTRL_FORMATTER_BP | VDP_CTRL_COMPDEL_BP);
|
|
/* activate compdel for small sync delays */
|
|
if ((pdata->vidout_delay_vs < 4) || (pdata->vidout_delay_hs < 4))
|
|
reg &= ~VDP_CTRL_COMPDEL_BP;
|
|
io_write(sd, REG_VDP_CTRL, reg);
|
|
|
|
/* Configure DE output signal: delay, polarity, and source */
|
|
reg = pdata->vidout_delay_de << DE_FREF_DELAY_SHIFT |
|
|
pdata->vidout_inv_de << DE_FREF_INV_SHIFT |
|
|
pdata->vidout_sel_de << DE_FREF_SEL_SHIFT;
|
|
io_write(sd, REG_DE_FREF, reg);
|
|
|
|
/* Configure HS/HREF output signal: delay, polarity, and source */
|
|
if (state->vid_fmt != OF_FMT_422_CCIR) {
|
|
reg = pdata->vidout_delay_hs << HS_HREF_DELAY_SHIFT |
|
|
pdata->vidout_inv_hs << HS_HREF_INV_SHIFT |
|
|
pdata->vidout_sel_hs << HS_HREF_SEL_SHIFT;
|
|
} else
|
|
reg = HS_HREF_SEL_NONE << HS_HREF_SEL_SHIFT;
|
|
io_write(sd, REG_HS_HREF, reg);
|
|
|
|
/* Configure VS/VREF output signal: delay, polarity, and source */
|
|
if (state->vid_fmt != OF_FMT_422_CCIR) {
|
|
reg = pdata->vidout_delay_vs << VS_VREF_DELAY_SHIFT |
|
|
pdata->vidout_inv_vs << VS_VREF_INV_SHIFT |
|
|
pdata->vidout_sel_vs << VS_VREF_SEL_SHIFT;
|
|
} else
|
|
reg = VS_VREF_SEL_NONE << VS_VREF_SEL_SHIFT;
|
|
io_write(sd, REG_VS_VREF, reg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Configure Audio output port signals */
|
|
static int
|
|
tda1997x_configure_audout(struct v4l2_subdev *sd, u8 channel_assignment)
|
|
{
|
|
struct tda1997x_state *state = to_state(sd);
|
|
struct tda1997x_platform_data *pdata = &state->pdata;
|
|
bool sp_used_by_fifo = 1;
|
|
u8 reg;
|
|
|
|
if (!pdata->audout_format)
|
|
return 0;
|
|
|
|
/* channel assignment (CEA-861-D Table 20) */
|
|
io_write(sd, REG_AUDIO_PATH, channel_assignment);
|
|
|
|
/* Audio output configuration */
|
|
reg = 0;
|
|
switch (pdata->audout_format) {
|
|
case AUDFMT_TYPE_I2S:
|
|
reg |= AUDCFG_BUS_I2S << AUDCFG_BUS_SHIFT;
|
|
break;
|
|
case AUDFMT_TYPE_SPDIF:
|
|
reg |= AUDCFG_BUS_SPDIF << AUDCFG_BUS_SHIFT;
|
|
break;
|
|
}
|
|
switch (state->audio_type) {
|
|
case AUDCFG_TYPE_PCM:
|
|
reg |= AUDCFG_TYPE_PCM << AUDCFG_TYPE_SHIFT;
|
|
break;
|
|
case AUDCFG_TYPE_OBA:
|
|
reg |= AUDCFG_TYPE_OBA << AUDCFG_TYPE_SHIFT;
|
|
break;
|
|
case AUDCFG_TYPE_DST:
|
|
reg |= AUDCFG_TYPE_DST << AUDCFG_TYPE_SHIFT;
|
|
sp_used_by_fifo = 0;
|
|
break;
|
|
case AUDCFG_TYPE_HBR:
|
|
reg |= AUDCFG_TYPE_HBR << AUDCFG_TYPE_SHIFT;
|
|
if (pdata->audout_layout == 1) {
|
|
/* demuxed via AP0:AP3 */
|
|
reg |= AUDCFG_HBR_DEMUX << AUDCFG_HBR_SHIFT;
|
|
if (pdata->audout_format == AUDFMT_TYPE_SPDIF)
|
|
sp_used_by_fifo = 0;
|
|
} else {
|
|
/* straight via AP0 */
|
|
reg |= AUDCFG_HBR_STRAIGHT << AUDCFG_HBR_SHIFT;
|
|
}
|
|
break;
|
|
}
|
|
if (pdata->audout_width == 32)
|
|
reg |= AUDCFG_I2SW_32 << AUDCFG_I2SW_SHIFT;
|
|
else
|
|
reg |= AUDCFG_I2SW_16 << AUDCFG_I2SW_SHIFT;
|
|
|
|
/* automatic hardware mute */
|
|
if (pdata->audio_auto_mute)
|
|
reg |= AUDCFG_AUTO_MUTE_EN;
|
|
/* clock polarity */
|
|
if (pdata->audout_invert_clk)
|
|
reg |= AUDCFG_CLK_INVERT;
|
|
io_write(sd, REG_AUDCFG, reg);
|
|
|
|
/* audio layout */
|
|
reg = (pdata->audout_layout) ? AUDIO_LAYOUT_LAYOUT1 : 0;
|
|
if (!pdata->audout_layoutauto)
|
|
reg |= AUDIO_LAYOUT_MANUAL;
|
|
if (sp_used_by_fifo)
|
|
reg |= AUDIO_LAYOUT_SP_FLAG;
|
|
io_write(sd, REG_AUDIO_LAYOUT, reg);
|
|
|
|
/* FIFO Latency value */
|
|
io_write(sd, REG_FIFO_LATENCY_VAL, 0x80);
|
|
|
|
/* Audio output port config */
|
|
if (sp_used_by_fifo) {
|
|
reg = AUDIO_OUT_ENABLE_AP0;
|
|
if (channel_assignment >= 0x01)
|
|
reg |= AUDIO_OUT_ENABLE_AP1;
|
|
if (channel_assignment >= 0x04)
|
|
reg |= AUDIO_OUT_ENABLE_AP2;
|
|
if (channel_assignment >= 0x0c)
|
|
reg |= AUDIO_OUT_ENABLE_AP3;
|
|
/* specific cases where AP1 is not used */
|
|
if ((channel_assignment == 0x04)
|
|
|| (channel_assignment == 0x08)
|
|
|| (channel_assignment == 0x0c)
|
|
|| (channel_assignment == 0x10)
|
|
|| (channel_assignment == 0x14)
|
|
|| (channel_assignment == 0x18)
|
|
|| (channel_assignment == 0x1c))
|
|
reg &= ~AUDIO_OUT_ENABLE_AP1;
|
|
/* specific cases where AP2 is not used */
|
|
if ((channel_assignment >= 0x14)
|
|
&& (channel_assignment <= 0x17))
|
|
reg &= ~AUDIO_OUT_ENABLE_AP2;
|
|
} else {
|
|
reg = AUDIO_OUT_ENABLE_AP3 |
|
|
AUDIO_OUT_ENABLE_AP2 |
|
|
AUDIO_OUT_ENABLE_AP1 |
|
|
AUDIO_OUT_ENABLE_AP0;
|
|
}
|
|
if (pdata->audout_format == AUDFMT_TYPE_I2S)
|
|
reg |= (AUDIO_OUT_ENABLE_ACLK | AUDIO_OUT_ENABLE_WS);
|
|
io_write(sd, REG_AUDIO_OUT_ENABLE, reg);
|
|
|
|
/* reset test mode to normal audio freq auto selection */
|
|
io_write(sd, REG_TEST_MODE, 0x00);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Soft Reset of specific hdmi info */
|
|
static int
|
|
tda1997x_hdmi_info_reset(struct v4l2_subdev *sd, u8 info_rst, bool reset_sus)
|
|
{
|
|
u8 reg;
|
|
|
|
/* reset infoframe engine packets */
|
|
reg = io_read(sd, REG_HDMI_INFO_RST);
|
|
io_write(sd, REG_HDMI_INFO_RST, info_rst);
|
|
|
|
/* if infoframe engine has been reset clear INT_FLG_MODE */
|
|
if (reg & RESET_IF) {
|
|
reg = io_read(sd, REG_INT_FLG_CLR_MODE);
|
|
io_write(sd, REG_INT_FLG_CLR_MODE, reg);
|
|
}
|
|
|
|
/* Disable REFTIM to restart start-up-sequencer (SUS) */
|
|
reg = io_read(sd, REG_RATE_CTRL);
|
|
reg &= ~RATE_REFTIM_ENABLE;
|
|
if (!reset_sus)
|
|
reg |= RATE_REFTIM_ENABLE;
|
|
reg = io_write(sd, REG_RATE_CTRL, reg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
tda1997x_power_mode(struct tda1997x_state *state, bool enable)
|
|
{
|
|
struct v4l2_subdev *sd = &state->sd;
|
|
u8 reg;
|
|
|
|
if (enable) {
|
|
/* Automatic control of TMDS */
|
|
io_write(sd, REG_PON_OVR_EN, PON_DIS);
|
|
/* Enable current bias unit */
|
|
io_write(sd, REG_CFG1, PON_EN);
|
|
/* Enable deep color PLL */
|
|
io_write(sd, REG_DEEP_PLL7_BYP, PON_DIS);
|
|
/* Output buffers active */
|
|
reg = io_read(sd, REG_OF);
|
|
reg &= ~OF_VP_ENABLE;
|
|
io_write(sd, REG_OF, reg);
|
|
} else {
|
|
/* Power down EDID mode sequence */
|
|
/* Output buffers in HiZ */
|
|
reg = io_read(sd, REG_OF);
|
|
reg |= OF_VP_ENABLE;
|
|
io_write(sd, REG_OF, reg);
|
|
/* Disable deep color PLL */
|
|
io_write(sd, REG_DEEP_PLL7_BYP, PON_EN);
|
|
/* Disable current bias unit */
|
|
io_write(sd, REG_CFG1, PON_DIS);
|
|
/* Manual control of TMDS */
|
|
io_write(sd, REG_PON_OVR_EN, PON_EN);
|
|
}
|
|
}
|
|
|
|
static bool
|
|
tda1997x_detect_tx_5v(struct v4l2_subdev *sd)
|
|
{
|
|
u8 reg = io_read(sd, REG_DETECT_5V);
|
|
|
|
return ((reg & DETECT_5V_SEL) ? 1 : 0);
|
|
}
|
|
|
|
static bool
|
|
tda1997x_detect_tx_hpd(struct v4l2_subdev *sd)
|
|
{
|
|
u8 reg = io_read(sd, REG_DETECT_5V);
|
|
|
|
return ((reg & DETECT_HPD) ? 1 : 0);
|
|
}
|
|
|
|
static int
|
|
tda1997x_detect_std(struct tda1997x_state *state,
|
|
struct v4l2_dv_timings *timings)
|
|
{
|
|
struct v4l2_subdev *sd = &state->sd;
|
|
u32 vper;
|
|
u16 hper;
|
|
u16 hsper;
|
|
int i;
|
|
|
|
/*
|
|
* Read the FMT registers
|
|
* REG_V_PER: Period of a frame (or two fields) in MCLK(27MHz) cycles
|
|
* REG_H_PER: Period of a line in MCLK(27MHz) cycles
|
|
* REG_HS_WIDTH: Period of horiz sync pulse in MCLK(27MHz) cycles
|
|
*/
|
|
vper = io_read24(sd, REG_V_PER) & MASK_VPER;
|
|
hper = io_read16(sd, REG_H_PER) & MASK_HPER;
|
|
hsper = io_read16(sd, REG_HS_WIDTH) & MASK_HSWIDTH;
|
|
v4l2_dbg(1, debug, sd, "Signal Timings: %u/%u/%u\n", vper, hper, hsper);
|
|
if (!vper || !hper || !hsper)
|
|
return -ENOLINK;
|
|
|
|
for (i = 0; v4l2_dv_timings_presets[i].bt.width; i++) {
|
|
const struct v4l2_bt_timings *bt;
|
|
u32 lines, width, _hper, _hsper;
|
|
u32 vmin, vmax, hmin, hmax, hsmin, hsmax;
|
|
bool vmatch, hmatch, hsmatch;
|
|
|
|
bt = &v4l2_dv_timings_presets[i].bt;
|
|
width = V4L2_DV_BT_FRAME_WIDTH(bt);
|
|
lines = V4L2_DV_BT_FRAME_HEIGHT(bt);
|
|
_hper = (u32)bt->pixelclock / width;
|
|
if (bt->interlaced)
|
|
lines /= 2;
|
|
/* vper +/- 0.7% */
|
|
vmin = ((27000000 / 1000) * 993) / _hper * lines;
|
|
vmax = ((27000000 / 1000) * 1007) / _hper * lines;
|
|
/* hper +/- 1.0% */
|
|
hmin = ((27000000 / 100) * 99) / _hper;
|
|
hmax = ((27000000 / 100) * 101) / _hper;
|
|
/* hsper +/- 2 (take care to avoid 32bit overflow) */
|
|
_hsper = 27000 * bt->hsync / ((u32)bt->pixelclock/1000);
|
|
hsmin = _hsper - 2;
|
|
hsmax = _hsper + 2;
|
|
|
|
/* vmatch matches the framerate */
|
|
vmatch = ((vper <= vmax) && (vper >= vmin)) ? 1 : 0;
|
|
/* hmatch matches the width */
|
|
hmatch = ((hper <= hmax) && (hper >= hmin)) ? 1 : 0;
|
|
/* hsmatch matches the hswidth */
|
|
hsmatch = ((hsper <= hsmax) && (hsper >= hsmin)) ? 1 : 0;
|
|
if (hmatch && vmatch && hsmatch) {
|
|
v4l2_print_dv_timings(sd->name, "Detected format: ",
|
|
&v4l2_dv_timings_presets[i],
|
|
false);
|
|
if (timings)
|
|
*timings = v4l2_dv_timings_presets[i];
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
v4l_err(state->client, "no resolution match for timings: %d/%d/%d\n",
|
|
vper, hper, hsper);
|
|
return -ERANGE;
|
|
}
|
|
|
|
/* some sort of errata workaround for chip revision 0 (N1) */
|
|
static void tda1997x_reset_n1(struct tda1997x_state *state)
|
|
{
|
|
struct v4l2_subdev *sd = &state->sd;
|
|
u8 reg;
|
|
|
|
/* clear HDMI mode flag in BCAPS */
|
|
io_write(sd, REG_CLK_CFG, CLK_CFG_SEL_ACLK_EN | CLK_CFG_SEL_ACLK);
|
|
io_write(sd, REG_PON_OVR_EN, PON_EN);
|
|
io_write(sd, REG_PON_CBIAS, PON_EN);
|
|
io_write(sd, REG_PON_PLL, PON_EN);
|
|
|
|
reg = io_read(sd, REG_MODE_REC_CFG1);
|
|
reg &= ~0x06;
|
|
reg |= 0x02;
|
|
io_write(sd, REG_MODE_REC_CFG1, reg);
|
|
io_write(sd, REG_CLK_CFG, CLK_CFG_DIS);
|
|
io_write(sd, REG_PON_OVR_EN, PON_DIS);
|
|
reg = io_read(sd, REG_MODE_REC_CFG1);
|
|
reg &= ~0x06;
|
|
io_write(sd, REG_MODE_REC_CFG1, reg);
|
|
}
|
|
|
|
/*
|
|
* Activity detection must only be notified when stable_clk_x AND active_x
|
|
* bits are set to 1. If only stable_clk_x bit is set to 1 but not
|
|
* active_x, it means that the TMDS clock is not in the defined range
|
|
* and activity detection must not be notified.
|
|
*/
|
|
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);
|
|
/* ignore if not active */
|
|
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);
|
|
/* ignore if not active */
|
|
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;
|
|
}
|
|
|
|
static void
|
|
set_rgb_quantization_range(struct tda1997x_state *state)
|
|
{
|
|
struct v4l2_hdmi_colorimetry *c = &state->colorimetry;
|
|
|
|
state->colorimetry = v4l2_hdmi_rx_colorimetry(&state->avi_infoframe,
|
|
NULL,
|
|
state->timings.bt.height);
|
|
/* If ycbcr_enc is V4L2_YCBCR_ENC_DEFAULT, we receive RGB */
|
|
if (c->ycbcr_enc == V4L2_YCBCR_ENC_DEFAULT) {
|
|
switch (state->rgb_quantization_range) {
|
|
case V4L2_DV_RGB_RANGE_LIMITED:
|
|
c->quantization = V4L2_QUANTIZATION_FULL_RANGE;
|
|
break;
|
|
case V4L2_DV_RGB_RANGE_FULL:
|
|
c->quantization = V4L2_QUANTIZATION_LIM_RANGE;
|
|
break;
|
|
}
|
|
}
|
|
v4l_dbg(1, debug, state->client,
|
|
"colorspace=%d/%d colorimetry=%d range=%s content=%d\n",
|
|
state->avi_infoframe.colorspace, c->colorspace,
|
|
state->avi_infoframe.colorimetry,
|
|
v4l2_quantization_names[c->quantization],
|
|
state->avi_infoframe.content_type);
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
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;
|
|
|
|
/* Auxiliary Video information (AVI) InfoFrame: see HDMI spec 8.2.1 */
|
|
case HDMI_INFOFRAME_TYPE_AVI:
|
|
state->avi_infoframe = frame.avi;
|
|
set_rgb_quantization_range(state);
|
|
|
|
/* configure upsampler: 0=bypass 1=repeatchroma 2=interpolate */
|
|
reg = io_read(sd, REG_PIX_REPEAT);
|
|
reg &= ~PIX_REPEAT_MASK_UP_SEL;
|
|
if (frame.avi.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_csc(sd);
|
|
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);
|
|
|
|
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;
|
|
}
|
|
if (debug)
|
|
tda1997x_detect_std(state, NULL);
|
|
/* notify user of change in resolution */
|
|
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;
|
|
}
|
|
|
|
/* Detection of +5V */
|
|
if (source & MASK_DET_5V) {
|
|
v4l2_ctrl_s_ctrl(state->detect_tx_5v_ctrl,
|
|
tda1997x_detect_tx_5v(sd));
|
|
}
|
|
}
|
|
|
|
static void tda1997x_irq_rate(struct tda1997x_state *state, u8 *flags)
|
|
{
|
|
struct v4l2_subdev *sd = &state->sd;
|
|
u8 reg, source;
|
|
|
|
u8 irq_status;
|
|
|
|
source = io_read(sd, REG_INT_FLG_CLR_RATE);
|
|
io_write(sd, REG_INT_FLG_CLR_RATE, source);
|
|
|
|
/* read status regs */
|
|
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->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;
|
|
}
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* v4l2_subdev_video_ops
|
|
*/
|
|
|
|
static int
|
|
tda1997x_g_input_status(struct v4l2_subdev *sd, u32 *status)
|
|
{
|
|
struct tda1997x_state *state = to_state(sd);
|
|
u32 vper;
|
|
u16 hper;
|
|
u16 hsper;
|
|
|
|
mutex_lock(&state->lock);
|
|
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;
|
|
/*
|
|
* The tda1997x supports A/B inputs but only a single output.
|
|
* The irq handler monitors for timing changes on both inputs and
|
|
* sets the input_detect array to 0|1 depending on signal presence.
|
|
* I believe selection of A vs B is automatic.
|
|
*
|
|
* The vper/hper/hsper registers provide the frame period, line period
|
|
* and horiz sync period (units of MCLK clock cycles (27MHz)) and
|
|
* testing shows these values to be random if no signal is present
|
|
* or locked.
|
|
*/
|
|
v4l2_dbg(1, debug, sd, "inputs:%d/%d timings:%d/%d/%d\n",
|
|
state->input_detect[0], state->input_detect[1],
|
|
vper, hper, hsper);
|
|
if (!state->input_detect[0] && !state->input_detect[1])
|
|
*status = V4L2_IN_ST_NO_SIGNAL;
|
|
else if (!vper || !hper || !hsper)
|
|
*status = V4L2_IN_ST_NO_SYNC;
|
|
else
|
|
*status = 0;
|
|
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);
|
|
|
|
v4l_dbg(1, debug, state->client, "%s\n", __func__);
|
|
|
|
if (v4l2_match_dv_timings(&state->timings, timings, 0, false))
|
|
return 0; /* no changes */
|
|
|
|
if (!v4l2_valid_dv_timings(timings, &tda1997x_dv_timings_cap,
|
|
NULL, NULL))
|
|
return -ERANGE;
|
|
|
|
mutex_lock(&state->lock);
|
|
state->timings = *timings;
|
|
/* setup frame detection window and VHREF timing generator */
|
|
tda1997x_configure_vhref(sd);
|
|
/* configure colorspace conversion */
|
|
tda1997x_configure_csc(sd);
|
|
mutex_unlock(&state->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
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__);
|
|
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);
|
|
|
|
v4l_dbg(1, debug, state->client, "%s\n", __func__);
|
|
memset(timings, 0, sizeof(struct v4l2_dv_timings));
|
|
mutex_lock(&state->lock);
|
|
tda1997x_detect_std(state, timings);
|
|
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,
|
|
};
|
|
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* v4l2_subdev_pad_ops
|
|
*/
|
|
|
|
static int tda1997x_init_cfg(struct v4l2_subdev *sd,
|
|
struct v4l2_subdev_pad_config *cfg)
|
|
{
|
|
struct tda1997x_state *state = to_state(sd);
|
|
struct v4l2_mbus_framefmt *mf;
|
|
|
|
mf = v4l2_subdev_get_try_format(sd, cfg, 0);
|
|
mf->code = state->mbus_codes[0];
|
|
|
|
return 0;
|
|
}
|
|
|
|
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);
|
|
|
|
v4l_dbg(1, debug, state->client, "%s %d\n", __func__, code->index);
|
|
if (code->index >= ARRAY_SIZE(state->mbus_codes))
|
|
return -EINVAL;
|
|
|
|
if (!state->mbus_codes[code->index])
|
|
return -EINVAL;
|
|
|
|
code->code = state->mbus_codes[code->index];
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void tda1997x_fill_format(struct tda1997x_state *state,
|
|
struct v4l2_mbus_framefmt *format)
|
|
{
|
|
const struct v4l2_bt_timings *bt;
|
|
|
|
memset(format, 0, sizeof(*format));
|
|
bt = &state->timings.bt;
|
|
format->width = bt->width;
|
|
format->height = bt->height;
|
|
format->colorspace = state->colorimetry.colorspace;
|
|
format->field = (bt->interlaced) ?
|
|
V4L2_FIELD_SEQ_TB : V4L2_FIELD_NONE;
|
|
}
|
|
|
|
static int tda1997x_get_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 pad=%d which=%d\n",
|
|
__func__, format->pad, format->which);
|
|
|
|
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 = fmt->code;
|
|
} else
|
|
format->format.code = state->mbus_code;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tda1997x_set_format(struct v4l2_subdev *sd,
|
|
struct v4l2_subdev_pad_config *cfg,
|
|
struct v4l2_subdev_format *format)
|
|
{
|
|
struct tda1997x_state *state = to_state(sd);
|
|
u32 code = 0;
|
|
int i;
|
|
|
|
v4l_dbg(1, debug, state->client, "%s pad=%d which=%d fmt=0x%x\n",
|
|
__func__, format->pad, format->which, format->format.code);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(state->mbus_codes); i++) {
|
|
if (format->format.code == state->mbus_codes[i]) {
|
|
code = state->mbus_codes[i];
|
|
break;
|
|
}
|
|
}
|
|
if (!code)
|
|
code = state->mbus_codes[0];
|
|
|
|
tda1997x_fill_format(state, &format->format);
|
|
format->format.code = code;
|
|
|
|
if (format->which == V4L2_SUBDEV_FORMAT_TRY) {
|
|
struct v4l2_mbus_framefmt *fmt;
|
|
|
|
fmt = v4l2_subdev_get_try_format(sd, cfg, format->pad);
|
|
*fmt = format->format;
|
|
} else {
|
|
int ret = tda1997x_setup_format(state, format->format.code);
|
|
|
|
if (ret)
|
|
return ret;
|
|
/* mbus_code has changed - re-configure csc/vidout */
|
|
tda1997x_configure_csc(sd);
|
|
tda1997x_configure_vidout(state);
|
|
}
|
|
|
|
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 (edid->start_block == 0 && edid->blocks == 0) {
|
|
edid->blocks = state->edid.blocks;
|
|
return 0;
|
|
}
|
|
|
|
if (!state->edid.present)
|
|
return -ENODATA;
|
|
|
|
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_disable_edid(sd);
|
|
return 0;
|
|
}
|
|
|
|
if (edid->blocks > 2) {
|
|
edid->blocks = 2;
|
|
return -E2BIG;
|
|
}
|
|
|
|
tda1997x_disable_edid(sd);
|
|
|
|
/* 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]);
|
|
|
|
tda1997x_enable_edid(sd);
|
|
|
|
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)
|
|
{
|
|
return v4l2_enum_dv_timings_cap(timings, &tda1997x_dv_timings_cap,
|
|
NULL, NULL);
|
|
}
|
|
|
|
static const struct v4l2_subdev_pad_ops tda1997x_pad_ops = {
|
|
.init_cfg = tda1997x_init_cfg,
|
|
.enum_mbus_code = tda1997x_enum_mbus_code,
|
|
.get_fmt = tda1997x_get_format,
|
|
.set_fmt = tda1997x_set_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_infoframe(struct v4l2_subdev *sd, int addr)
|
|
{
|
|
struct tda1997x_state *state = to_state(sd);
|
|
union hdmi_infoframe frame;
|
|
u8 buffer[40];
|
|
int len, err;
|
|
|
|
/* read data */
|
|
len = io_readn(sd, addr, sizeof(buffer), buffer);
|
|
v4l2_dbg(1, debug, sd, "infoframe: addr=%d len=%d\n", addr, len);
|
|
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;
|
|
}
|
|
hdmi_infoframe_log(KERN_INFO, &state->client->dev, &frame);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tda1997x_log_status(struct v4l2_subdev *sd)
|
|
{
|
|
struct tda1997x_state *state = to_state(sd);
|
|
struct v4l2_dv_timings timings;
|
|
struct hdmi_avi_infoframe *avi = &state->avi_infoframe;
|
|
|
|
v4l2_info(sd, "-----Chip status-----\n");
|
|
v4l2_info(sd, "Chip: %s N%d\n", state->info->name,
|
|
state->chip_revision + 1);
|
|
v4l2_info(sd, "EDID Enabled: %s\n", state->edid.present ? "yes" : "no");
|
|
|
|
v4l2_info(sd, "-----Signal status-----\n");
|
|
v4l2_info(sd, "Cable detected (+5V power): %s\n",
|
|
tda1997x_detect_tx_5v(sd) ? "yes" : "no");
|
|
v4l2_info(sd, "HPD detected: %s\n",
|
|
tda1997x_detect_tx_hpd(sd) ? "yes" : "no");
|
|
|
|
v4l2_info(sd, "-----Video Timings-----\n");
|
|
switch (tda1997x_detect_std(state, &timings)) {
|
|
case -ENOLINK:
|
|
v4l2_info(sd, "No video detected\n");
|
|
break;
|
|
case -ERANGE:
|
|
v4l2_info(sd, "Invalid signal detected\n");
|
|
break;
|
|
}
|
|
v4l2_print_dv_timings(sd->name, "Configured format: ",
|
|
&state->timings, true);
|
|
|
|
v4l2_info(sd, "-----Color space-----\n");
|
|
v4l2_info(sd, "Input color space: %s %s %s",
|
|
hdmi_colorspace_names[avi->colorspace],
|
|
(avi->colorspace == HDMI_COLORSPACE_RGB) ? "" :
|
|
hdmi_colorimetry_names[avi->colorimetry],
|
|
v4l2_quantization_names[state->colorimetry.quantization]);
|
|
v4l2_info(sd, "Output color space: %s",
|
|
vidfmt_names[state->vid_fmt]);
|
|
v4l2_info(sd, "Color space conversion: %s", state->conv ?
|
|
state->conv->name : "None");
|
|
|
|
v4l2_info(sd, "-----Audio-----\n");
|
|
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, "-----Infoframes-----\n");
|
|
tda1997x_log_infoframe(sd, AUD_IF);
|
|
tda1997x_log_infoframe(sd, SPD_IF);
|
|
tda1997x_log_infoframe(sd, AVI_IF);
|
|
|
|
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,
|
|
};
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* v4l2_controls
|
|
*/
|
|
|
|
static int tda1997x_s_ctrl(struct v4l2_ctrl *ctrl)
|
|
{
|
|
struct v4l2_subdev *sd = to_sd(ctrl);
|
|
struct tda1997x_state *state = to_state(sd);
|
|
|
|
switch (ctrl->id) {
|
|
/* allow overriding the default RGB quantization range */
|
|
case V4L2_CID_DV_RX_RGB_RANGE:
|
|
state->rgb_quantization_range = ctrl->val;
|
|
set_rgb_quantization_range(state);
|
|
tda1997x_configure_csc(sd);
|
|
return 0;
|
|
}
|
|
|
|
return -EINVAL;
|
|
};
|
|
|
|
static int tda1997x_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
|
|
{
|
|
struct v4l2_subdev *sd = to_sd(ctrl);
|
|
struct tda1997x_state *state = to_state(sd);
|
|
|
|
if (ctrl->id == V4L2_CID_DV_RX_IT_CONTENT_TYPE) {
|
|
ctrl->val = state->avi_infoframe.content_type;
|
|
return 0;
|
|
}
|
|
return -EINVAL;
|
|
};
|
|
|
|
static const struct v4l2_ctrl_ops tda1997x_ctrl_ops = {
|
|
.s_ctrl = tda1997x_s_ctrl,
|
|
.g_volatile_ctrl = tda1997x_g_volatile_ctrl,
|
|
};
|
|
|
|
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);
|
|
/* 5V detect and HDP pulse end */
|
|
io_write(sd, REG_INT_MASK_DDC, MASK_DET_5V);
|
|
/* don't care about AFE/MODE */
|
|
io_write(sd, REG_INT_MASK_AFE, 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);
|
|
|
|
/* 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);
|
|
} else {
|
|
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);
|
|
pdata->vidout_bus_type = bus_cfg.bus_type;
|
|
|
|
/* 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_component *component = dai->component;
|
|
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(component->dev, "failed to constrain samplerate to %dHz\n",
|
|
rate);
|
|
return err;
|
|
}
|
|
dev_info(component->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_component *component)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static void tda1997x_codec_remove(struct snd_soc_component *component)
|
|
{
|
|
}
|
|
|
|
static struct snd_soc_component_driver tda1997x_codec_driver = {
|
|
.probe = tda1997x_codec_probe,
|
|
.remove = tda1997x_codec_remove,
|
|
.idle_bias_on = 1,
|
|
.use_pmdown_time = 1,
|
|
.endianness = 1,
|
|
.non_legacy_dai_naming = 1,
|
|
};
|
|
|
|
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;
|
|
struct v4l2_ctrl_handler *hdl;
|
|
struct v4l2_ctrl *ctrl;
|
|
static const struct v4l2_dv_timings cea1920x1080 =
|
|
V4L2_DV_BT_CEA_1920X1080P60;
|
|
u32 *mbus_codes;
|
|
int i, 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;
|
|
|
|
INIT_DELAYED_WORK(&state->delayed_work_enable_hpd,
|
|
tda1997x_delayed_work_enable_hpd);
|
|
|
|
/* 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.function = MEDIA_ENT_F_DV_DECODER;
|
|
sd->entity.ops = &tda1997x_media_ops;
|
|
|
|
/* set allowed mbus modes based on chip, bus-type, and bus-width */
|
|
i = 0;
|
|
mbus_codes = state->mbus_codes;
|
|
switch (state->info->type) {
|
|
case TDA19973:
|
|
switch (pdata->vidout_bus_type) {
|
|
case V4L2_MBUS_PARALLEL:
|
|
switch (pdata->vidout_bus_width) {
|
|
case 36:
|
|
mbus_codes[i++] = MEDIA_BUS_FMT_RGB121212_1X36;
|
|
mbus_codes[i++] = MEDIA_BUS_FMT_YUV12_1X36;
|
|
/* fall-through */
|
|
case 24:
|
|
mbus_codes[i++] = MEDIA_BUS_FMT_UYVY12_1X24;
|
|
break;
|
|
}
|
|
break;
|
|
case V4L2_MBUS_BT656:
|
|
switch (pdata->vidout_bus_width) {
|
|
case 36:
|
|
case 24:
|
|
case 12:
|
|
mbus_codes[i++] = MEDIA_BUS_FMT_UYVY12_2X12;
|
|
mbus_codes[i++] = MEDIA_BUS_FMT_UYVY10_2X10;
|
|
mbus_codes[i++] = MEDIA_BUS_FMT_UYVY8_2X8;
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
case TDA19971:
|
|
switch (pdata->vidout_bus_type) {
|
|
case V4L2_MBUS_PARALLEL:
|
|
switch (pdata->vidout_bus_width) {
|
|
case 24:
|
|
mbus_codes[i++] = MEDIA_BUS_FMT_RGB888_1X24;
|
|
mbus_codes[i++] = MEDIA_BUS_FMT_YUV8_1X24;
|
|
mbus_codes[i++] = MEDIA_BUS_FMT_UYVY12_1X24;
|
|
/* fall through */
|
|
case 20:
|
|
mbus_codes[i++] = MEDIA_BUS_FMT_UYVY10_1X20;
|
|
/* fall through */
|
|
case 16:
|
|
mbus_codes[i++] = MEDIA_BUS_FMT_UYVY8_1X16;
|
|
break;
|
|
}
|
|
break;
|
|
case V4L2_MBUS_BT656:
|
|
switch (pdata->vidout_bus_width) {
|
|
case 24:
|
|
case 20:
|
|
case 16:
|
|
case 12:
|
|
mbus_codes[i++] = MEDIA_BUS_FMT_UYVY12_2X12;
|
|
/* fall through */
|
|
case 10:
|
|
mbus_codes[i++] = MEDIA_BUS_FMT_UYVY10_2X10;
|
|
/* fall through */
|
|
case 8:
|
|
mbus_codes[i++] = MEDIA_BUS_FMT_UYVY8_2X8;
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
if (WARN_ON(i > ARRAY_SIZE(state->mbus_codes))) {
|
|
ret = -EINVAL;
|
|
goto err_free_mutex;
|
|
}
|
|
|
|
/* default format */
|
|
tda1997x_setup_format(state, state->mbus_codes[0]);
|
|
state->timings = cea1920x1080;
|
|
|
|
/*
|
|
* default to SRGB full range quantization
|
|
* (in case we don't get an infoframe such as DVI signal
|
|
*/
|
|
state->colorimetry.colorspace = V4L2_COLORSPACE_SRGB;
|
|
state->colorimetry.quantization = V4L2_QUANTIZATION_FULL_RANGE;
|
|
|
|
/* 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 %d formats available\n",
|
|
pdata->vidout_bus_width,
|
|
(pdata->vidout_bus_type == V4L2_MBUS_PARALLEL) ?
|
|
"parallel" : "BT656",
|
|
i);
|
|
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;
|
|
|
|
/* control handlers */
|
|
hdl = &state->hdl;
|
|
v4l2_ctrl_handler_init(hdl, 3);
|
|
ctrl = v4l2_ctrl_new_std_menu(hdl, &tda1997x_ctrl_ops,
|
|
V4L2_CID_DV_RX_IT_CONTENT_TYPE,
|
|
V4L2_DV_IT_CONTENT_TYPE_NO_ITC, 0,
|
|
V4L2_DV_IT_CONTENT_TYPE_NO_ITC);
|
|
if (ctrl)
|
|
ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE;
|
|
/* custom controls */
|
|
state->detect_tx_5v_ctrl = v4l2_ctrl_new_std(hdl, NULL,
|
|
V4L2_CID_DV_RX_POWER_PRESENT, 0, 1, 0, 0);
|
|
state->rgb_quantization_range_ctrl = v4l2_ctrl_new_std_menu(hdl,
|
|
&tda1997x_ctrl_ops,
|
|
V4L2_CID_DV_RX_RGB_RANGE, V4L2_DV_RGB_RANGE_FULL, 0,
|
|
V4L2_DV_RGB_RANGE_AUTO);
|
|
state->sd.ctrl_handler = hdl;
|
|
if (hdl->error) {
|
|
ret = hdl->error;
|
|
goto err_free_handler;
|
|
}
|
|
v4l2_ctrl_handler_setup(hdl);
|
|
|
|
/* 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_handler;
|
|
}
|
|
|
|
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 = devm_snd_soc_register_component(&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_handler:
|
|
v4l2_ctrl_handler_free(&state->hdl);
|
|
err_free_mutex:
|
|
cancel_delayed_work(&state->delayed_work_enable_hpd);
|
|
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) {
|
|
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);
|
|
v4l2_ctrl_handler_free(&state->hdl);
|
|
regulator_bulk_disable(TDA1997X_NUM_SUPPLIES, state->supplies);
|
|
i2c_unregister_device(state->client_cec);
|
|
cancel_delayed_work(&state->delayed_work_enable_hpd);
|
|
mutex_destroy(&state->page_lock);
|
|
mutex_destroy(&state->lock);
|
|
|
|
kfree(state);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct i2c_driver tda1997x_i2c_driver = {
|
|
.driver = {
|
|
.name = "tda1997x",
|
|
.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");
|