Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,8 @@
# SPDX-License-Identifier: GPL-2.0-only
adv748x-objs := \
adv748x-afe.o \
adv748x-core.o \
adv748x-csi2.o \
adv748x-hdmi.o
obj-$(CONFIG_VIDEO_ADV748X) += adv748x.o
@@ -0,0 +1,539 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Driver for Analog Devices ADV748X 8 channel analog front end (AFE) receiver
* with standard definition processor (SDP)
*
* Copyright (C) 2017 Renesas Electronics Corp.
*/
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/v4l2-dv-timings.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-device.h>
#include <media/v4l2-dv-timings.h>
#include <media/v4l2-ioctl.h>
#include "adv748x.h"
/* -----------------------------------------------------------------------------
* SDP
*/
#define ADV748X_AFE_STD_AD_PAL_BG_NTSC_J_SECAM 0x0
#define ADV748X_AFE_STD_AD_PAL_BG_NTSC_J_SECAM_PED 0x1
#define ADV748X_AFE_STD_AD_PAL_N_NTSC_J_SECAM 0x2
#define ADV748X_AFE_STD_AD_PAL_N_NTSC_M_SECAM 0x3
#define ADV748X_AFE_STD_NTSC_J 0x4
#define ADV748X_AFE_STD_NTSC_M 0x5
#define ADV748X_AFE_STD_PAL60 0x6
#define ADV748X_AFE_STD_NTSC_443 0x7
#define ADV748X_AFE_STD_PAL_BG 0x8
#define ADV748X_AFE_STD_PAL_N 0x9
#define ADV748X_AFE_STD_PAL_M 0xa
#define ADV748X_AFE_STD_PAL_M_PED 0xb
#define ADV748X_AFE_STD_PAL_COMB_N 0xc
#define ADV748X_AFE_STD_PAL_COMB_N_PED 0xd
#define ADV748X_AFE_STD_PAL_SECAM 0xe
#define ADV748X_AFE_STD_PAL_SECAM_PED 0xf
static int adv748x_afe_read_ro_map(struct adv748x_state *state, u8 reg)
{
int ret;
/* Select SDP Read-Only Main Map */
ret = sdp_write(state, ADV748X_SDP_MAP_SEL,
ADV748X_SDP_MAP_SEL_RO_MAIN);
if (ret < 0)
return ret;
return sdp_read(state, reg);
}
static int adv748x_afe_status(struct adv748x_afe *afe, u32 *signal,
v4l2_std_id *std)
{
struct adv748x_state *state = adv748x_afe_to_state(afe);
int info;
/* Read status from reg 0x10 of SDP RO Map */
info = adv748x_afe_read_ro_map(state, ADV748X_SDP_RO_10);
if (info < 0)
return info;
if (signal)
*signal = info & ADV748X_SDP_RO_10_IN_LOCK ?
0 : V4L2_IN_ST_NO_SIGNAL;
if (!std)
return 0;
/* Standard not valid if there is no signal */
if (!(info & ADV748X_SDP_RO_10_IN_LOCK)) {
*std = V4L2_STD_UNKNOWN;
return 0;
}
switch (info & 0x70) {
case 0x00:
*std = V4L2_STD_NTSC;
break;
case 0x10:
*std = V4L2_STD_NTSC_443;
break;
case 0x20:
*std = V4L2_STD_PAL_M;
break;
case 0x30:
*std = V4L2_STD_PAL_60;
break;
case 0x40:
*std = V4L2_STD_PAL;
break;
case 0x50:
*std = V4L2_STD_SECAM;
break;
case 0x60:
*std = V4L2_STD_PAL_Nc | V4L2_STD_PAL_N;
break;
case 0x70:
*std = V4L2_STD_SECAM;
break;
default:
*std = V4L2_STD_UNKNOWN;
break;
}
return 0;
}
static void adv748x_afe_fill_format(struct adv748x_afe *afe,
struct v4l2_mbus_framefmt *fmt)
{
memset(fmt, 0, sizeof(*fmt));
fmt->code = MEDIA_BUS_FMT_UYVY8_1X16;
fmt->colorspace = V4L2_COLORSPACE_SMPTE170M;
fmt->field = V4L2_FIELD_ALTERNATE;
fmt->width = 720;
fmt->height = afe->curr_norm & V4L2_STD_525_60 ? 480 : 576;
/* Field height */
fmt->height /= 2;
}
static int adv748x_afe_std(v4l2_std_id std)
{
if (std == V4L2_STD_PAL_60)
return ADV748X_AFE_STD_PAL60;
if (std == V4L2_STD_NTSC_443)
return ADV748X_AFE_STD_NTSC_443;
if (std == V4L2_STD_PAL_N)
return ADV748X_AFE_STD_PAL_N;
if (std == V4L2_STD_PAL_M)
return ADV748X_AFE_STD_PAL_M;
if (std == V4L2_STD_PAL_Nc)
return ADV748X_AFE_STD_PAL_COMB_N;
if (std & V4L2_STD_NTSC)
return ADV748X_AFE_STD_NTSC_M;
if (std & V4L2_STD_PAL)
return ADV748X_AFE_STD_PAL_BG;
if (std & V4L2_STD_SECAM)
return ADV748X_AFE_STD_PAL_SECAM;
return -EINVAL;
}
static void adv748x_afe_set_video_standard(struct adv748x_state *state,
int sdpstd)
{
sdp_clrset(state, ADV748X_SDP_VID_SEL, ADV748X_SDP_VID_SEL_MASK,
(sdpstd & 0xf) << ADV748X_SDP_VID_SEL_SHIFT);
}
int adv748x_afe_s_input(struct adv748x_afe *afe, unsigned int input)
{
struct adv748x_state *state = adv748x_afe_to_state(afe);
return sdp_write(state, ADV748X_SDP_INSEL, input);
}
/* -----------------------------------------------------------------------------
* v4l2_subdev_video_ops
*/
static int adv748x_afe_g_std(struct v4l2_subdev *sd, v4l2_std_id *norm)
{
struct adv748x_afe *afe = adv748x_sd_to_afe(sd);
*norm = afe->curr_norm;
return 0;
}
static int adv748x_afe_s_std(struct v4l2_subdev *sd, v4l2_std_id std)
{
struct adv748x_afe *afe = adv748x_sd_to_afe(sd);
struct adv748x_state *state = adv748x_afe_to_state(afe);
int afe_std = adv748x_afe_std(std);
if (afe_std < 0)
return afe_std;
mutex_lock(&state->mutex);
adv748x_afe_set_video_standard(state, afe_std);
afe->curr_norm = std;
mutex_unlock(&state->mutex);
return 0;
}
static int adv748x_afe_querystd(struct v4l2_subdev *sd, v4l2_std_id *std)
{
struct adv748x_afe *afe = adv748x_sd_to_afe(sd);
struct adv748x_state *state = adv748x_afe_to_state(afe);
int afe_std;
int ret;
mutex_lock(&state->mutex);
if (afe->streaming) {
ret = -EBUSY;
goto unlock;
}
/* Set auto detect mode */
adv748x_afe_set_video_standard(state,
ADV748X_AFE_STD_AD_PAL_BG_NTSC_J_SECAM);
msleep(100);
/* Read detected standard */
ret = adv748x_afe_status(afe, NULL, std);
afe_std = adv748x_afe_std(afe->curr_norm);
if (afe_std < 0)
goto unlock;
/* Restore original state */
adv748x_afe_set_video_standard(state, afe_std);
unlock:
mutex_unlock(&state->mutex);
return ret;
}
static int adv748x_afe_g_tvnorms(struct v4l2_subdev *sd, v4l2_std_id *norm)
{
*norm = V4L2_STD_ALL;
return 0;
}
static int adv748x_afe_g_input_status(struct v4l2_subdev *sd, u32 *status)
{
struct adv748x_afe *afe = adv748x_sd_to_afe(sd);
struct adv748x_state *state = adv748x_afe_to_state(afe);
int ret;
mutex_lock(&state->mutex);
ret = adv748x_afe_status(afe, status, NULL);
mutex_unlock(&state->mutex);
return ret;
}
static int adv748x_afe_s_stream(struct v4l2_subdev *sd, int enable)
{
struct adv748x_afe *afe = adv748x_sd_to_afe(sd);
struct adv748x_state *state = adv748x_afe_to_state(afe);
u32 signal = V4L2_IN_ST_NO_SIGNAL;
int ret;
mutex_lock(&state->mutex);
if (enable) {
ret = adv748x_afe_s_input(afe, afe->input);
if (ret)
goto unlock;
}
ret = adv748x_tx_power(afe->tx, enable);
if (ret)
goto unlock;
afe->streaming = enable;
adv748x_afe_status(afe, &signal, NULL);
if (signal != V4L2_IN_ST_NO_SIGNAL)
adv_dbg(state, "Detected SDP signal\n");
else
adv_dbg(state, "Couldn't detect SDP video signal\n");
unlock:
mutex_unlock(&state->mutex);
return ret;
}
static const struct v4l2_subdev_video_ops adv748x_afe_video_ops = {
.g_std = adv748x_afe_g_std,
.s_std = adv748x_afe_s_std,
.querystd = adv748x_afe_querystd,
.g_tvnorms = adv748x_afe_g_tvnorms,
.g_input_status = adv748x_afe_g_input_status,
.s_stream = adv748x_afe_s_stream,
};
/* -----------------------------------------------------------------------------
* v4l2_subdev_pad_ops
*/
static int adv748x_afe_propagate_pixelrate(struct adv748x_afe *afe)
{
struct v4l2_subdev *tx;
tx = adv748x_get_remote_sd(&afe->pads[ADV748X_AFE_SOURCE]);
if (!tx)
return -ENOLINK;
/*
* The ADV748x ADC sampling frequency is twice the externally supplied
* clock whose frequency is required to be 28.63636 MHz. It oversamples
* with a factor of 4 resulting in a pixel rate of 14.3180180 MHz.
*/
return adv748x_csi2_set_pixelrate(tx, 14318180);
}
static int adv748x_afe_enum_mbus_code(struct v4l2_subdev *sd,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_mbus_code_enum *code)
{
if (code->index != 0)
return -EINVAL;
code->code = MEDIA_BUS_FMT_UYVY8_1X16;
return 0;
}
static int adv748x_afe_get_format(struct v4l2_subdev *sd,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_format *sdformat)
{
struct adv748x_afe *afe = adv748x_sd_to_afe(sd);
struct v4l2_mbus_framefmt *mbusformat;
/* It makes no sense to get the format of the analog sink pads */
if (sdformat->pad != ADV748X_AFE_SOURCE)
return -EINVAL;
if (sdformat->which == V4L2_SUBDEV_FORMAT_TRY) {
mbusformat = v4l2_subdev_state_get_format(sd_state,
sdformat->pad);
sdformat->format = *mbusformat;
} else {
adv748x_afe_fill_format(afe, &sdformat->format);
adv748x_afe_propagate_pixelrate(afe);
}
return 0;
}
static int adv748x_afe_set_format(struct v4l2_subdev *sd,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_format *sdformat)
{
struct v4l2_mbus_framefmt *mbusformat;
/* It makes no sense to get the format of the analog sink pads */
if (sdformat->pad != ADV748X_AFE_SOURCE)
return -EINVAL;
if (sdformat->which == V4L2_SUBDEV_FORMAT_ACTIVE)
return adv748x_afe_get_format(sd, sd_state, sdformat);
mbusformat = v4l2_subdev_state_get_format(sd_state, sdformat->pad);
*mbusformat = sdformat->format;
return 0;
}
static const struct v4l2_subdev_pad_ops adv748x_afe_pad_ops = {
.enum_mbus_code = adv748x_afe_enum_mbus_code,
.set_fmt = adv748x_afe_set_format,
.get_fmt = adv748x_afe_get_format,
};
/* -----------------------------------------------------------------------------
* v4l2_subdev_ops
*/
static const struct v4l2_subdev_ops adv748x_afe_ops = {
.video = &adv748x_afe_video_ops,
.pad = &adv748x_afe_pad_ops,
};
/* -----------------------------------------------------------------------------
* Controls
*/
static const char * const afe_ctrl_frp_menu[] = {
"Disabled",
"Solid Blue",
"Color Bars",
"Grey Ramp",
"Cb Ramp",
"Cr Ramp",
"Boundary"
};
static int adv748x_afe_s_ctrl(struct v4l2_ctrl *ctrl)
{
struct adv748x_afe *afe = adv748x_ctrl_to_afe(ctrl);
struct adv748x_state *state = adv748x_afe_to_state(afe);
bool enable;
int ret;
ret = sdp_write(state, 0x0e, 0x00);
if (ret < 0)
return ret;
switch (ctrl->id) {
case V4L2_CID_BRIGHTNESS:
ret = sdp_write(state, ADV748X_SDP_BRI, ctrl->val);
break;
case V4L2_CID_HUE:
/* Hue is inverted according to HSL chart */
ret = sdp_write(state, ADV748X_SDP_HUE, -ctrl->val);
break;
case V4L2_CID_CONTRAST:
ret = sdp_write(state, ADV748X_SDP_CON, ctrl->val);
break;
case V4L2_CID_SATURATION:
ret = sdp_write(state, ADV748X_SDP_SD_SAT_U, ctrl->val);
if (ret)
break;
ret = sdp_write(state, ADV748X_SDP_SD_SAT_V, ctrl->val);
break;
case V4L2_CID_TEST_PATTERN:
enable = !!ctrl->val;
/* Enable/Disable Color bar test patterns */
ret = sdp_clrset(state, ADV748X_SDP_DEF, ADV748X_SDP_DEF_VAL_EN,
enable);
if (ret)
break;
ret = sdp_clrset(state, ADV748X_SDP_FRP, ADV748X_SDP_FRP_MASK,
enable ? ctrl->val - 1 : 0);
break;
default:
return -EINVAL;
}
return ret;
}
static const struct v4l2_ctrl_ops adv748x_afe_ctrl_ops = {
.s_ctrl = adv748x_afe_s_ctrl,
};
static int adv748x_afe_init_controls(struct adv748x_afe *afe)
{
struct adv748x_state *state = adv748x_afe_to_state(afe);
v4l2_ctrl_handler_init(&afe->ctrl_hdl, 5);
/* Use our mutex for the controls */
afe->ctrl_hdl.lock = &state->mutex;
v4l2_ctrl_new_std(&afe->ctrl_hdl, &adv748x_afe_ctrl_ops,
V4L2_CID_BRIGHTNESS, ADV748X_SDP_BRI_MIN,
ADV748X_SDP_BRI_MAX, 1, ADV748X_SDP_BRI_DEF);
v4l2_ctrl_new_std(&afe->ctrl_hdl, &adv748x_afe_ctrl_ops,
V4L2_CID_CONTRAST, ADV748X_SDP_CON_MIN,
ADV748X_SDP_CON_MAX, 1, ADV748X_SDP_CON_DEF);
v4l2_ctrl_new_std(&afe->ctrl_hdl, &adv748x_afe_ctrl_ops,
V4L2_CID_SATURATION, ADV748X_SDP_SAT_MIN,
ADV748X_SDP_SAT_MAX, 1, ADV748X_SDP_SAT_DEF);
v4l2_ctrl_new_std(&afe->ctrl_hdl, &adv748x_afe_ctrl_ops,
V4L2_CID_HUE, ADV748X_SDP_HUE_MIN,
ADV748X_SDP_HUE_MAX, 1, ADV748X_SDP_HUE_DEF);
v4l2_ctrl_new_std_menu_items(&afe->ctrl_hdl, &adv748x_afe_ctrl_ops,
V4L2_CID_TEST_PATTERN,
ARRAY_SIZE(afe_ctrl_frp_menu) - 1,
0, 0, afe_ctrl_frp_menu);
afe->sd.ctrl_handler = &afe->ctrl_hdl;
if (afe->ctrl_hdl.error) {
v4l2_ctrl_handler_free(&afe->ctrl_hdl);
return afe->ctrl_hdl.error;
}
return v4l2_ctrl_handler_setup(&afe->ctrl_hdl);
}
int adv748x_afe_init(struct adv748x_afe *afe)
{
struct adv748x_state *state = adv748x_afe_to_state(afe);
int ret;
unsigned int i;
afe->input = 0;
afe->streaming = false;
afe->curr_norm = V4L2_STD_NTSC_M;
adv748x_subdev_init(&afe->sd, state, &adv748x_afe_ops,
MEDIA_ENT_F_ATV_DECODER, "afe");
/* Identify the first connector found as a default input if set */
for (i = ADV748X_PORT_AIN0; i <= ADV748X_PORT_AIN7; i++) {
/* Inputs and ports are 1-indexed to match the data sheet */
if (state->endpoints[i]) {
afe->input = i;
break;
}
}
adv748x_afe_s_input(afe, afe->input);
adv_dbg(state, "AFE Default input set to %d\n", afe->input);
/* Entity pads and sinks are 0-indexed to match the pads */
for (i = ADV748X_AFE_SINK_AIN0; i <= ADV748X_AFE_SINK_AIN7; i++)
afe->pads[i].flags = MEDIA_PAD_FL_SINK;
afe->pads[ADV748X_AFE_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
ret = media_entity_pads_init(&afe->sd.entity, ADV748X_AFE_NR_PADS,
afe->pads);
if (ret)
return ret;
ret = adv748x_afe_init_controls(afe);
if (ret)
goto error;
return 0;
error:
media_entity_cleanup(&afe->sd.entity);
return ret;
}
void adv748x_afe_cleanup(struct adv748x_afe *afe)
{
v4l2_device_unregister_subdev(&afe->sd);
media_entity_cleanup(&afe->sd.entity);
v4l2_ctrl_handler_free(&afe->ctrl_hdl);
}
@@ -0,0 +1,858 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Driver for Analog Devices ADV748X HDMI receiver with AFE
*
* Copyright (C) 2017 Renesas Electronics Corp.
*
* Authors:
* Koji Matsuoka <koji.matsuoka.xm@renesas.com>
* Niklas Söderlund <niklas.soderlund@ragnatech.se>
* Kieran Bingham <kieran.bingham@ideasonboard.com>
*/
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of_graph.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/v4l2-dv-timings.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-device.h>
#include <media/v4l2-dv-timings.h>
#include <media/v4l2-fwnode.h>
#include <media/v4l2-ioctl.h>
#include "adv748x.h"
/* -----------------------------------------------------------------------------
* Register manipulation
*/
#define ADV748X_REGMAP_CONF(n) \
{ \
.name = n, \
.reg_bits = 8, \
.val_bits = 8, \
.max_register = 0xff, \
.cache_type = REGCACHE_NONE, \
}
static const struct regmap_config adv748x_regmap_cnf[] = {
ADV748X_REGMAP_CONF("io"),
ADV748X_REGMAP_CONF("dpll"),
ADV748X_REGMAP_CONF("cp"),
ADV748X_REGMAP_CONF("hdmi"),
ADV748X_REGMAP_CONF("edid"),
ADV748X_REGMAP_CONF("repeater"),
ADV748X_REGMAP_CONF("infoframe"),
ADV748X_REGMAP_CONF("cbus"),
ADV748X_REGMAP_CONF("cec"),
ADV748X_REGMAP_CONF("sdp"),
ADV748X_REGMAP_CONF("txa"),
ADV748X_REGMAP_CONF("txb"),
};
static int adv748x_configure_regmap(struct adv748x_state *state, int region)
{
int err;
if (!state->i2c_clients[region])
return -ENODEV;
state->regmap[region] =
devm_regmap_init_i2c(state->i2c_clients[region],
&adv748x_regmap_cnf[region]);
if (IS_ERR(state->regmap[region])) {
err = PTR_ERR(state->regmap[region]);
adv_err(state,
"Error initializing regmap %d with error %d\n",
region, err);
return -EINVAL;
}
return 0;
}
struct adv748x_register_map {
const char *name;
u8 default_addr;
};
static const struct adv748x_register_map adv748x_default_addresses[] = {
[ADV748X_PAGE_IO] = { "main", 0x70 },
[ADV748X_PAGE_DPLL] = { "dpll", 0x26 },
[ADV748X_PAGE_CP] = { "cp", 0x22 },
[ADV748X_PAGE_HDMI] = { "hdmi", 0x34 },
[ADV748X_PAGE_EDID] = { "edid", 0x36 },
[ADV748X_PAGE_REPEATER] = { "repeater", 0x32 },
[ADV748X_PAGE_INFOFRAME] = { "infoframe", 0x31 },
[ADV748X_PAGE_CBUS] = { "cbus", 0x30 },
[ADV748X_PAGE_CEC] = { "cec", 0x41 },
[ADV748X_PAGE_SDP] = { "sdp", 0x79 },
[ADV748X_PAGE_TXB] = { "txb", 0x48 },
[ADV748X_PAGE_TXA] = { "txa", 0x4a },
};
static int adv748x_read_check(struct adv748x_state *state,
int client_page, u8 reg)
{
struct i2c_client *client = state->i2c_clients[client_page];
int err;
unsigned int val;
err = regmap_read(state->regmap[client_page], reg, &val);
if (err) {
adv_err(state, "error reading %02x, %02x\n",
client->addr, reg);
return err;
}
return val;
}
int adv748x_read(struct adv748x_state *state, u8 page, u8 reg)
{
return adv748x_read_check(state, page, reg);
}
int adv748x_write(struct adv748x_state *state, u8 page, u8 reg, u8 value)
{
return regmap_write(state->regmap[page], reg, value);
}
static int adv748x_write_check(struct adv748x_state *state, u8 page, u8 reg,
u8 value, int *error)
{
if (*error)
return *error;
*error = adv748x_write(state, page, reg, value);
return *error;
}
/* adv748x_write_block(): Write raw data with a maximum of I2C_SMBUS_BLOCK_MAX
* size to one or more registers.
*
* A value of zero will be returned on success, a negative errno will
* be returned in error cases.
*/
int adv748x_write_block(struct adv748x_state *state, int client_page,
unsigned int init_reg, const void *val,
size_t val_len)
{
struct regmap *regmap = state->regmap[client_page];
if (val_len > I2C_SMBUS_BLOCK_MAX)
val_len = I2C_SMBUS_BLOCK_MAX;
return regmap_raw_write(regmap, init_reg, val, val_len);
}
static int adv748x_set_slave_addresses(struct adv748x_state *state)
{
struct i2c_client *client;
unsigned int i;
u8 io_reg;
for (i = ADV748X_PAGE_DPLL; i < ADV748X_PAGE_MAX; ++i) {
io_reg = ADV748X_IO_SLAVE_ADDR_BASE + i;
client = state->i2c_clients[i];
io_write(state, io_reg, client->addr << 1);
}
return 0;
}
static void adv748x_unregister_clients(struct adv748x_state *state)
{
unsigned int i;
for (i = 1; i < ARRAY_SIZE(state->i2c_clients); ++i)
i2c_unregister_device(state->i2c_clients[i]);
}
static int adv748x_initialise_clients(struct adv748x_state *state)
{
unsigned int i;
int ret;
for (i = ADV748X_PAGE_DPLL; i < ADV748X_PAGE_MAX; ++i) {
state->i2c_clients[i] = i2c_new_ancillary_device(
state->client,
adv748x_default_addresses[i].name,
adv748x_default_addresses[i].default_addr);
if (IS_ERR(state->i2c_clients[i])) {
adv_err(state, "failed to create i2c client %u\n", i);
return PTR_ERR(state->i2c_clients[i]);
}
ret = adv748x_configure_regmap(state, i);
if (ret)
return ret;
}
return 0;
}
/**
* struct adv748x_reg_value - Register write instruction
* @page: Regmap page identifier
* @reg: I2C register
* @value: value to write to @page at @reg
*/
struct adv748x_reg_value {
u8 page;
u8 reg;
u8 value;
};
static int adv748x_write_regs(struct adv748x_state *state,
const struct adv748x_reg_value *regs)
{
int ret;
for (; regs->page != ADV748X_PAGE_EOR; regs++) {
ret = adv748x_write(state, regs->page, regs->reg, regs->value);
if (ret < 0) {
adv_err(state, "Error regs page: 0x%02x reg: 0x%02x\n",
regs->page, regs->reg);
return ret;
}
}
return 0;
}
/* -----------------------------------------------------------------------------
* TXA and TXB
*/
static int adv748x_power_up_tx(struct adv748x_csi2 *tx)
{
struct adv748x_state *state = tx->state;
u8 page = is_txa(tx) ? ADV748X_PAGE_TXA : ADV748X_PAGE_TXB;
int ret = 0;
/* Enable n-lane MIPI */
adv748x_write_check(state, page, 0x00, 0x80 | tx->active_lanes, &ret);
/* Set Auto DPHY Timing */
adv748x_write_check(state, page, 0x00, 0xa0 | tx->active_lanes, &ret);
/* ADI Required Write */
if (tx->src == &state->hdmi.sd) {
adv748x_write_check(state, page, 0xdb, 0x10, &ret);
adv748x_write_check(state, page, 0xd6, 0x07, &ret);
} else {
adv748x_write_check(state, page, 0xd2, 0x40, &ret);
}
adv748x_write_check(state, page, 0xc4, 0x0a, &ret);
adv748x_write_check(state, page, 0x71, 0x33, &ret);
adv748x_write_check(state, page, 0x72, 0x11, &ret);
/* i2c_dphy_pwdn - 1'b0 */
adv748x_write_check(state, page, 0xf0, 0x00, &ret);
/* ADI Required Writes*/
adv748x_write_check(state, page, 0x31, 0x82, &ret);
adv748x_write_check(state, page, 0x1e, 0x40, &ret);
/* i2c_mipi_pll_en - 1'b1 */
adv748x_write_check(state, page, 0xda, 0x01, &ret);
usleep_range(2000, 2500);
/* Power-up CSI-TX */
adv748x_write_check(state, page, 0x00, 0x20 | tx->active_lanes, &ret);
usleep_range(1000, 1500);
/* ADI Required Writes */
adv748x_write_check(state, page, 0xc1, 0x2b, &ret);
usleep_range(1000, 1500);
adv748x_write_check(state, page, 0x31, 0x80, &ret);
return ret;
}
static int adv748x_power_down_tx(struct adv748x_csi2 *tx)
{
struct adv748x_state *state = tx->state;
u8 page = is_txa(tx) ? ADV748X_PAGE_TXA : ADV748X_PAGE_TXB;
int ret = 0;
/* ADI Required Writes */
adv748x_write_check(state, page, 0x31, 0x82, &ret);
adv748x_write_check(state, page, 0x1e, 0x00, &ret);
/* Enable n-lane MIPI */
adv748x_write_check(state, page, 0x00, 0x80 | tx->active_lanes, &ret);
/* i2c_mipi_pll_en - 1'b1 */
adv748x_write_check(state, page, 0xda, 0x01, &ret);
/* ADI Required Write */
adv748x_write_check(state, page, 0xc1, 0x3b, &ret);
return ret;
}
int adv748x_tx_power(struct adv748x_csi2 *tx, bool on)
{
int val;
if (!is_tx_enabled(tx))
return 0;
val = tx_read(tx, ADV748X_CSI_FS_AS_LS);
if (val < 0)
return val;
/*
* This test against BIT(6) is not documented by the datasheet, but was
* specified in the downstream driver.
* Track with a WARN_ONCE to determine if it is ever set by HW.
*/
WARN_ONCE((on && val & ADV748X_CSI_FS_AS_LS_UNKNOWN),
"Enabling with unknown bit set");
return on ? adv748x_power_up_tx(tx) : adv748x_power_down_tx(tx);
}
/* -----------------------------------------------------------------------------
* Media Operations
*/
static int adv748x_link_setup(struct media_entity *entity,
const struct media_pad *local,
const struct media_pad *remote, u32 flags)
{
struct v4l2_subdev *rsd = media_entity_to_v4l2_subdev(remote->entity);
struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity);
struct adv748x_state *state = v4l2_get_subdevdata(sd);
struct adv748x_csi2 *tx = adv748x_sd_to_csi2(sd);
bool enable = flags & MEDIA_LNK_FL_ENABLED;
u8 io10_mask = ADV748X_IO_10_CSI1_EN |
ADV748X_IO_10_CSI4_EN |
ADV748X_IO_10_CSI4_IN_SEL_AFE;
u8 io10 = 0;
/* Refuse to enable multiple links to the same TX at the same time. */
if (enable && tx->src)
return -EINVAL;
/* Set or clear the source (HDMI or AFE) and the current TX. */
if (rsd == &state->afe.sd)
state->afe.tx = enable ? tx : NULL;
else
state->hdmi.tx = enable ? tx : NULL;
tx->src = enable ? rsd : NULL;
if (state->afe.tx) {
/* AFE Requires TXA enabled, even when output to TXB */
io10 |= ADV748X_IO_10_CSI4_EN;
if (is_txa(tx)) {
/*
* Output from the SD-core (480i and 576i) from the TXA
* interface requires reducing the number of enabled
* data lanes in order to guarantee a valid link
* frequency.
*/
tx->active_lanes = min(tx->num_lanes, 2U);
io10 |= ADV748X_IO_10_CSI4_IN_SEL_AFE;
} else {
/* TXB has a single data lane, no need to adjust. */
io10 |= ADV748X_IO_10_CSI1_EN;
}
}
if (state->hdmi.tx) {
/*
* Restore the number of active lanes, in case we have gone
* through an AFE->TXA streaming sessions.
*/
tx->active_lanes = tx->num_lanes;
io10 |= ADV748X_IO_10_CSI4_EN;
}
return io_clrset(state, ADV748X_IO_10, io10_mask, io10);
}
static const struct media_entity_operations adv748x_tx_media_ops = {
.link_setup = adv748x_link_setup,
.link_validate = v4l2_subdev_link_validate,
};
static const struct media_entity_operations adv748x_media_ops = {
.link_validate = v4l2_subdev_link_validate,
};
/* -----------------------------------------------------------------------------
* HW setup
*/
/* Initialize CP Core with RGB888 format. */
static const struct adv748x_reg_value adv748x_init_hdmi[] = {
/* Disable chip powerdown & Enable HDMI Rx block */
{ADV748X_PAGE_IO, 0x00, 0x40},
{ADV748X_PAGE_REPEATER, 0x40, 0x83}, /* Enable HDCP 1.1 */
{ADV748X_PAGE_HDMI, 0x00, 0x08},/* Foreground Channel = A */
{ADV748X_PAGE_HDMI, 0x98, 0xff},/* ADI Required Write */
{ADV748X_PAGE_HDMI, 0x99, 0xa3},/* ADI Required Write */
{ADV748X_PAGE_HDMI, 0x9a, 0x00},/* ADI Required Write */
{ADV748X_PAGE_HDMI, 0x9b, 0x0a},/* ADI Required Write */
{ADV748X_PAGE_HDMI, 0x9d, 0x40},/* ADI Required Write */
{ADV748X_PAGE_HDMI, 0xcb, 0x09},/* ADI Required Write */
{ADV748X_PAGE_HDMI, 0x3d, 0x10},/* ADI Required Write */
{ADV748X_PAGE_HDMI, 0x3e, 0x7b},/* ADI Required Write */
{ADV748X_PAGE_HDMI, 0x3f, 0x5e},/* ADI Required Write */
{ADV748X_PAGE_HDMI, 0x4e, 0xfe},/* ADI Required Write */
{ADV748X_PAGE_HDMI, 0x4f, 0x18},/* ADI Required Write */
{ADV748X_PAGE_HDMI, 0x57, 0xa3},/* ADI Required Write */
{ADV748X_PAGE_HDMI, 0x58, 0x04},/* ADI Required Write */
{ADV748X_PAGE_HDMI, 0x85, 0x10},/* ADI Required Write */
{ADV748X_PAGE_HDMI, 0x83, 0x00},/* Enable All Terminations */
{ADV748X_PAGE_HDMI, 0xa3, 0x01},/* ADI Required Write */
{ADV748X_PAGE_HDMI, 0xbe, 0x00},/* ADI Required Write */
{ADV748X_PAGE_HDMI, 0x6c, 0x01},/* HPA Manual Enable */
{ADV748X_PAGE_HDMI, 0xf8, 0x01},/* HPA Asserted */
{ADV748X_PAGE_HDMI, 0x0f, 0x00},/* Audio Mute Speed Set to Fastest */
/* (Smallest Step Size) */
{ADV748X_PAGE_IO, 0x04, 0x02}, /* RGB Out of CP */
{ADV748X_PAGE_IO, 0x12, 0xf0}, /* CSC Depends on ip Packets, SDR 444 */
{ADV748X_PAGE_IO, 0x17, 0x80}, /* Luma & Chroma can reach 254d */
{ADV748X_PAGE_IO, 0x03, 0x86}, /* CP-Insert_AV_Code */
{ADV748X_PAGE_CP, 0x7c, 0x00}, /* ADI Required Write */
{ADV748X_PAGE_IO, 0x0c, 0xe0}, /* Enable LLC_DLL & Double LLC Timing */
{ADV748X_PAGE_IO, 0x0e, 0xdd}, /* LLC/PIX/SPI PINS TRISTATED AUD */
{ADV748X_PAGE_EOR, 0xff, 0xff} /* End of register table */
};
/* Initialize AFE core with YUV8 format. */
static const struct adv748x_reg_value adv748x_init_afe[] = {
{ADV748X_PAGE_IO, 0x00, 0x30}, /* Disable chip powerdown Rx */
{ADV748X_PAGE_IO, 0xf2, 0x01}, /* Enable I2C Read Auto-Increment */
{ADV748X_PAGE_IO, 0x0e, 0xff}, /* LLC/PIX/AUD/SPI PINS TRISTATED */
{ADV748X_PAGE_SDP, 0x0f, 0x00}, /* Exit Power Down Mode */
{ADV748X_PAGE_SDP, 0x52, 0xcd}, /* ADI Required Write */
{ADV748X_PAGE_SDP, 0x0e, 0x80}, /* ADI Required Write */
{ADV748X_PAGE_SDP, 0x9c, 0x00}, /* ADI Required Write */
{ADV748X_PAGE_SDP, 0x9c, 0xff}, /* ADI Required Write */
{ADV748X_PAGE_SDP, 0x0e, 0x00}, /* ADI Required Write */
/* ADI recommended writes for improved video quality */
{ADV748X_PAGE_SDP, 0x80, 0x51}, /* ADI Required Write */
{ADV748X_PAGE_SDP, 0x81, 0x51}, /* ADI Required Write */
{ADV748X_PAGE_SDP, 0x82, 0x68}, /* ADI Required Write */
{ADV748X_PAGE_SDP, 0x03, 0x42}, /* Tri-S Output , PwrDwn 656 pads */
{ADV748X_PAGE_SDP, 0x04, 0xb5}, /* ITU-R BT.656-4 compatible */
{ADV748X_PAGE_SDP, 0x13, 0x00}, /* ADI Required Write */
{ADV748X_PAGE_SDP, 0x17, 0x41}, /* Select SH1 */
{ADV748X_PAGE_SDP, 0x31, 0x12}, /* ADI Required Write */
{ADV748X_PAGE_SDP, 0xe6, 0x4f}, /* V bit end pos manually in NTSC */
{ADV748X_PAGE_EOR, 0xff, 0xff} /* End of register table */
};
static int adv748x_sw_reset(struct adv748x_state *state)
{
int ret;
ret = io_write(state, ADV748X_IO_REG_FF, ADV748X_IO_REG_FF_MAIN_RESET);
if (ret)
return ret;
usleep_range(5000, 6000);
/* Disable CEC Wakeup from power-down mode */
ret = io_clrset(state, ADV748X_IO_REG_01, ADV748X_IO_REG_01_PWRDN_MASK,
ADV748X_IO_REG_01_PWRDNB);
if (ret)
return ret;
/* Enable I2C Read Auto-Increment for consecutive reads */
return io_write(state, ADV748X_IO_REG_F2,
ADV748X_IO_REG_F2_READ_AUTO_INC);
}
static int adv748x_reset(struct adv748x_state *state)
{
int ret;
u8 regval = 0;
ret = adv748x_sw_reset(state);
if (ret < 0)
return ret;
ret = adv748x_set_slave_addresses(state);
if (ret < 0)
return ret;
/* Initialize CP and AFE cores. */
ret = adv748x_write_regs(state, adv748x_init_hdmi);
if (ret)
return ret;
ret = adv748x_write_regs(state, adv748x_init_afe);
if (ret)
return ret;
adv748x_afe_s_input(&state->afe, state->afe.input);
adv_dbg(state, "AFE Default input set to %d\n", state->afe.input);
/* Reset TXA and TXB */
adv748x_tx_power(&state->txa, 1);
adv748x_tx_power(&state->txa, 0);
adv748x_tx_power(&state->txb, 1);
adv748x_tx_power(&state->txb, 0);
/* Disable chip powerdown & Enable HDMI Rx block */
io_write(state, ADV748X_IO_PD, ADV748X_IO_PD_RX_EN);
/* Conditionally enable TXa and TXb. */
if (is_tx_enabled(&state->txa)) {
regval |= ADV748X_IO_10_CSI4_EN;
adv748x_csi2_set_virtual_channel(&state->txa, 0);
}
if (is_tx_enabled(&state->txb)) {
regval |= ADV748X_IO_10_CSI1_EN;
adv748x_csi2_set_virtual_channel(&state->txb, 0);
}
io_write(state, ADV748X_IO_10, regval);
/* Use vid_std and v_freq as freerun resolution for CP */
cp_clrset(state, ADV748X_CP_CLMP_POS, ADV748X_CP_CLMP_POS_DIS_AUTO,
ADV748X_CP_CLMP_POS_DIS_AUTO);
return 0;
}
static int adv748x_identify_chip(struct adv748x_state *state)
{
int msb, lsb;
lsb = io_read(state, ADV748X_IO_CHIP_REV_ID_1);
msb = io_read(state, ADV748X_IO_CHIP_REV_ID_2);
if (lsb < 0 || msb < 0) {
adv_err(state, "Failed to read chip revision\n");
return -EIO;
}
adv_info(state, "chip found @ 0x%02x revision %02x%02x\n",
state->client->addr << 1, lsb, msb);
return 0;
}
/* -----------------------------------------------------------------------------
* Suspend / Resume
*/
static int __maybe_unused adv748x_resume_early(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct adv748x_state *state = i2c_get_clientdata(client);
return adv748x_reset(state);
}
/* -----------------------------------------------------------------------------
* i2c driver
*/
void adv748x_subdev_init(struct v4l2_subdev *sd, struct adv748x_state *state,
const struct v4l2_subdev_ops *ops, u32 function,
const char *ident)
{
v4l2_subdev_init(sd, ops);
sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
/* the owner is the same as the i2c_client's driver owner */
sd->owner = state->dev->driver->owner;
sd->dev = state->dev;
v4l2_set_subdevdata(sd, state);
/* initialize name */
snprintf(sd->name, sizeof(sd->name), "%s %d-%04x %s",
state->dev->driver->name,
i2c_adapter_id(state->client->adapter),
state->client->addr, ident);
sd->entity.function = function;
sd->entity.ops = is_tx(adv748x_sd_to_csi2(sd)) ?
&adv748x_tx_media_ops : &adv748x_media_ops;
}
static int adv748x_parse_csi2_lanes(struct adv748x_state *state,
unsigned int port,
struct device_node *ep)
{
struct v4l2_fwnode_endpoint vep = { .bus_type = V4L2_MBUS_CSI2_DPHY };
unsigned int num_lanes;
int ret;
if (port != ADV748X_PORT_TXA && port != ADV748X_PORT_TXB)
return 0;
ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep), &vep);
if (ret)
return ret;
num_lanes = vep.bus.mipi_csi2.num_data_lanes;
if (vep.base.port == ADV748X_PORT_TXA) {
if (num_lanes != 1 && num_lanes != 2 && num_lanes != 4) {
adv_err(state, "TXA: Invalid number (%u) of lanes\n",
num_lanes);
return -EINVAL;
}
state->txa.num_lanes = num_lanes;
state->txa.active_lanes = num_lanes;
adv_dbg(state, "TXA: using %u lanes\n", state->txa.num_lanes);
}
if (vep.base.port == ADV748X_PORT_TXB) {
if (num_lanes != 1) {
adv_err(state, "TXB: Invalid number (%u) of lanes\n",
num_lanes);
return -EINVAL;
}
state->txb.num_lanes = num_lanes;
state->txb.active_lanes = num_lanes;
adv_dbg(state, "TXB: using %u lanes\n", state->txb.num_lanes);
}
return 0;
}
static int adv748x_parse_dt(struct adv748x_state *state)
{
struct device_node *ep_np = NULL;
struct of_endpoint ep;
bool out_found = false;
bool in_found = false;
int ret;
for_each_endpoint_of_node(state->dev->of_node, ep_np) {
of_graph_parse_endpoint(ep_np, &ep);
adv_info(state, "Endpoint %pOF on port %d", ep.local_node,
ep.port);
if (ep.port >= ADV748X_PORT_MAX) {
adv_err(state, "Invalid endpoint %pOF on port %d",
ep.local_node, ep.port);
continue;
}
if (state->endpoints[ep.port]) {
adv_err(state,
"Multiple port endpoints are not supported");
continue;
}
of_node_get(ep_np);
state->endpoints[ep.port] = ep_np;
/*
* At least one input endpoint and one output endpoint shall
* be defined.
*/
if (ep.port < ADV748X_PORT_TXA)
in_found = true;
else
out_found = true;
/* Store number of CSI-2 lanes used for TXA and TXB. */
ret = adv748x_parse_csi2_lanes(state, ep.port, ep_np);
if (ret)
return ret;
}
return in_found && out_found ? 0 : -ENODEV;
}
static void adv748x_dt_cleanup(struct adv748x_state *state)
{
unsigned int i;
for (i = 0; i < ADV748X_PORT_MAX; i++)
of_node_put(state->endpoints[i]);
}
static int adv748x_probe(struct i2c_client *client)
{
struct adv748x_state *state;
int ret;
/* Check if the adapter supports the needed features */
if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
return -EIO;
state = devm_kzalloc(&client->dev, sizeof(*state), GFP_KERNEL);
if (!state)
return -ENOMEM;
mutex_init(&state->mutex);
state->dev = &client->dev;
state->client = client;
state->i2c_clients[ADV748X_PAGE_IO] = client;
i2c_set_clientdata(client, state);
/*
* We can not use container_of to get back to the state with two TXs;
* Initialize the TXs's fields unconditionally on the endpoint
* presence to access them later.
*/
state->txa.state = state->txb.state = state;
state->txa.page = ADV748X_PAGE_TXA;
state->txb.page = ADV748X_PAGE_TXB;
state->txa.port = ADV748X_PORT_TXA;
state->txb.port = ADV748X_PORT_TXB;
/* Discover and process ports declared by the Device tree endpoints */
ret = adv748x_parse_dt(state);
if (ret) {
adv_err(state, "Failed to parse device tree");
goto err_free_mutex;
}
/* Configure IO Regmap region */
ret = adv748x_configure_regmap(state, ADV748X_PAGE_IO);
if (ret) {
adv_err(state, "Error configuring IO regmap region");
goto err_cleanup_dt;
}
ret = adv748x_identify_chip(state);
if (ret) {
adv_err(state, "Failed to identify chip");
goto err_cleanup_dt;
}
/* Configure remaining pages as I2C clients with regmap access */
ret = adv748x_initialise_clients(state);
if (ret) {
adv_err(state, "Failed to setup client regmap pages");
goto err_cleanup_clients;
}
/* SW reset ADV748X to its default values */
ret = adv748x_reset(state);
if (ret) {
adv_err(state, "Failed to reset hardware");
goto err_cleanup_clients;
}
/* Initialise HDMI */
ret = adv748x_hdmi_init(&state->hdmi);
if (ret) {
adv_err(state, "Failed to probe HDMI");
goto err_cleanup_clients;
}
/* Initialise AFE */
ret = adv748x_afe_init(&state->afe);
if (ret) {
adv_err(state, "Failed to probe AFE");
goto err_cleanup_hdmi;
}
/* Initialise TXA */
ret = adv748x_csi2_init(state, &state->txa);
if (ret) {
adv_err(state, "Failed to probe TXA");
goto err_cleanup_afe;
}
/* Initialise TXB */
ret = adv748x_csi2_init(state, &state->txb);
if (ret) {
adv_err(state, "Failed to probe TXB");
goto err_cleanup_txa;
}
return 0;
err_cleanup_txa:
adv748x_csi2_cleanup(&state->txa);
err_cleanup_afe:
adv748x_afe_cleanup(&state->afe);
err_cleanup_hdmi:
adv748x_hdmi_cleanup(&state->hdmi);
err_cleanup_clients:
adv748x_unregister_clients(state);
err_cleanup_dt:
adv748x_dt_cleanup(state);
err_free_mutex:
mutex_destroy(&state->mutex);
return ret;
}
static void adv748x_remove(struct i2c_client *client)
{
struct adv748x_state *state = i2c_get_clientdata(client);
adv748x_afe_cleanup(&state->afe);
adv748x_hdmi_cleanup(&state->hdmi);
adv748x_csi2_cleanup(&state->txa);
adv748x_csi2_cleanup(&state->txb);
adv748x_unregister_clients(state);
adv748x_dt_cleanup(state);
mutex_destroy(&state->mutex);
}
static const struct of_device_id adv748x_of_table[] = {
{ .compatible = "adi,adv7481", },
{ .compatible = "adi,adv7482", },
{ }
};
MODULE_DEVICE_TABLE(of, adv748x_of_table);
static const struct dev_pm_ops adv748x_pm_ops = {
SET_LATE_SYSTEM_SLEEP_PM_OPS(NULL, adv748x_resume_early)
};
static struct i2c_driver adv748x_driver = {
.driver = {
.name = "adv748x",
.of_match_table = adv748x_of_table,
.pm = &adv748x_pm_ops,
},
.probe = adv748x_probe,
.remove = adv748x_remove,
};
module_i2c_driver(adv748x_driver);
MODULE_AUTHOR("Kieran Bingham <kieran.bingham@ideasonboard.com>");
MODULE_DESCRIPTION("ADV748X video decoder");
MODULE_LICENSE("GPL");
@@ -0,0 +1,393 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Driver for Analog Devices ADV748X CSI-2 Transmitter
*
* Copyright (C) 2017 Renesas Electronics Corp.
*/
#include <linux/module.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include "adv748x.h"
static const unsigned int adv748x_csi2_txa_fmts[] = {
MEDIA_BUS_FMT_UYVY8_1X16,
MEDIA_BUS_FMT_RGB888_1X24,
};
static const unsigned int adv748x_csi2_txb_fmts[] = {
MEDIA_BUS_FMT_UYVY8_1X16,
};
int adv748x_csi2_set_virtual_channel(struct adv748x_csi2 *tx, unsigned int vc)
{
return tx_write(tx, ADV748X_CSI_VC_REF, vc << ADV748X_CSI_VC_REF_SHIFT);
}
/**
* adv748x_csi2_register_link : Register and link internal entities
*
* @tx: CSI2 private entity
* @v4l2_dev: Video registration device
* @src: Source subdevice to establish link
* @src_pad: Pad number of source to link to this @tx
* @enable: Link enabled flag
*
* Ensure that the subdevice is registered against the v4l2_device, and link the
* source pad to the sink pad of the CSI2 bus entity.
*/
static int adv748x_csi2_register_link(struct adv748x_csi2 *tx,
struct v4l2_device *v4l2_dev,
struct v4l2_subdev *src,
unsigned int src_pad,
bool enable)
{
int ret;
if (!src->v4l2_dev) {
ret = v4l2_device_register_subdev(v4l2_dev, src);
if (ret)
return ret;
}
ret = media_create_pad_link(&src->entity, src_pad,
&tx->sd.entity, ADV748X_CSI2_SINK,
enable ? MEDIA_LNK_FL_ENABLED : 0);
if (ret)
return ret;
if (enable)
tx->src = src;
return 0;
}
/* -----------------------------------------------------------------------------
* v4l2_subdev_internal_ops
*/
static int adv748x_csi2_init_state(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state)
{
static const struct v4l2_mbus_framefmt adv748x_csi2_default_fmt = {
.width = 1280,
.height = 720,
.code = MEDIA_BUS_FMT_UYVY8_1X16,
.colorspace = V4L2_COLORSPACE_SRGB,
.field = V4L2_FIELD_NONE,
.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT,
.quantization = V4L2_QUANTIZATION_DEFAULT,
.xfer_func = V4L2_XFER_FUNC_DEFAULT,
};
struct v4l2_mbus_framefmt *fmt;
fmt = v4l2_subdev_state_get_format(state, ADV748X_CSI2_SINK);
*fmt = adv748x_csi2_default_fmt;
fmt = v4l2_subdev_state_get_format(state, ADV748X_CSI2_SOURCE);
*fmt = adv748x_csi2_default_fmt;
return 0;
}
/*
* We use the internal registered operation to be able to ensure that our
* incremental subdevices (not connected in the forward path) can be registered
* against the resulting video path and media device.
*/
static int adv748x_csi2_registered(struct v4l2_subdev *sd)
{
struct adv748x_csi2 *tx = adv748x_sd_to_csi2(sd);
struct adv748x_state *state = tx->state;
int ret;
adv_dbg(state, "Registered %s (%s)", is_txa(tx) ? "TXA":"TXB",
sd->name);
/*
* Link TXA to AFE and HDMI, and TXB to AFE only as TXB cannot output
* HDMI.
*
* The HDMI->TXA link is enabled by default, as is the AFE->TXB one.
*/
if (is_afe_enabled(state)) {
ret = adv748x_csi2_register_link(tx, sd->v4l2_dev,
&state->afe.sd,
ADV748X_AFE_SOURCE,
is_txb(tx));
if (ret)
return ret;
/* TXB can output AFE signals only. */
if (is_txb(tx))
state->afe.tx = tx;
}
/* Register link to HDMI for TXA only. */
if (is_txb(tx) || !is_hdmi_enabled(state))
return 0;
ret = adv748x_csi2_register_link(tx, sd->v4l2_dev, &state->hdmi.sd,
ADV748X_HDMI_SOURCE, true);
if (ret)
return ret;
/* The default HDMI output is TXA. */
state->hdmi.tx = tx;
return 0;
}
static const struct v4l2_subdev_internal_ops adv748x_csi2_internal_ops = {
.init_state = adv748x_csi2_init_state,
.registered = adv748x_csi2_registered,
};
/* -----------------------------------------------------------------------------
* v4l2_subdev_video_ops
*/
static int adv748x_csi2_s_stream(struct v4l2_subdev *sd, int enable)
{
struct adv748x_csi2 *tx = adv748x_sd_to_csi2(sd);
struct v4l2_subdev *src;
src = adv748x_get_remote_sd(&tx->pads[ADV748X_CSI2_SINK]);
if (!src)
return -EPIPE;
return v4l2_subdev_call(src, video, s_stream, enable);
}
static const struct v4l2_subdev_video_ops adv748x_csi2_video_ops = {
.s_stream = adv748x_csi2_s_stream,
};
/* -----------------------------------------------------------------------------
* v4l2_subdev_pad_ops
*
* The CSI2 bus pads are ignorant to the data sizes or formats.
* But we must support setting the pad formats for format propagation.
*/
static int adv748x_csi2_enum_mbus_code(struct v4l2_subdev *sd,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_mbus_code_enum *code)
{
struct adv748x_csi2 *tx = adv748x_sd_to_csi2(sd);
const unsigned int *codes = is_txa(tx) ?
adv748x_csi2_txa_fmts :
adv748x_csi2_txb_fmts;
size_t num_fmts = is_txa(tx) ? ARRAY_SIZE(adv748x_csi2_txa_fmts)
: ARRAY_SIZE(adv748x_csi2_txb_fmts);
/*
* The format available on the source pad is the one applied on the sink
* pad.
*/
if (code->pad == ADV748X_CSI2_SOURCE) {
struct v4l2_mbus_framefmt *fmt;
if (code->index)
return -EINVAL;
fmt = v4l2_subdev_state_get_format(sd_state, ADV748X_CSI2_SINK);
code->code = fmt->code;
return 0;
}
if (code->index >= num_fmts)
return -EINVAL;
code->code = codes[code->index];
return 0;
}
static bool adv748x_csi2_is_fmt_supported(struct adv748x_csi2 *tx, u32 code)
{
const unsigned int *codes = is_txa(tx) ?
adv748x_csi2_txa_fmts :
adv748x_csi2_txb_fmts;
size_t num_fmts = is_txa(tx) ? ARRAY_SIZE(adv748x_csi2_txa_fmts)
: ARRAY_SIZE(adv748x_csi2_txb_fmts);
for (unsigned int i = 0; i < num_fmts; i++) {
if (codes[i] == code)
return true;
}
return false;
}
static int adv748x_csi2_set_format(struct v4l2_subdev *sd,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_format *sdformat)
{
struct adv748x_csi2 *tx = adv748x_sd_to_csi2(sd);
struct v4l2_mbus_framefmt *mbusformat;
if (sdformat->pad == ADV748X_CSI2_SOURCE)
return v4l2_subdev_get_fmt(sd, sd_state, sdformat);
/*
* Make sure the format is supported, if not default it to
* UYVY8 as it's supported by both TXes.
*/
if (!adv748x_csi2_is_fmt_supported(tx, sdformat->format.code))
sdformat->format.code = MEDIA_BUS_FMT_UYVY8_1X16;
mbusformat = v4l2_subdev_state_get_format(sd_state, sdformat->pad);
*mbusformat = sdformat->format;
/* Propagate format to the source pad. */
mbusformat = v4l2_subdev_state_get_format(sd_state, ADV748X_CSI2_SOURCE);
*mbusformat = sdformat->format;
return 0;
}
static int adv748x_csi2_get_mbus_config(struct v4l2_subdev *sd, unsigned int pad,
struct v4l2_mbus_config *config)
{
struct adv748x_csi2 *tx = adv748x_sd_to_csi2(sd);
if (pad != ADV748X_CSI2_SOURCE)
return -EINVAL;
config->type = V4L2_MBUS_CSI2_DPHY;
config->bus.mipi_csi2.num_data_lanes = tx->active_lanes;
return 0;
}
static const struct v4l2_subdev_pad_ops adv748x_csi2_pad_ops = {
.enum_mbus_code = adv748x_csi2_enum_mbus_code,
.get_fmt = v4l2_subdev_get_fmt,
.set_fmt = adv748x_csi2_set_format,
.get_mbus_config = adv748x_csi2_get_mbus_config,
};
/* -----------------------------------------------------------------------------
* v4l2_subdev_ops
*/
static const struct v4l2_subdev_ops adv748x_csi2_ops = {
.video = &adv748x_csi2_video_ops,
.pad = &adv748x_csi2_pad_ops,
};
/* -----------------------------------------------------------------------------
* Subdev module and controls
*/
int adv748x_csi2_set_pixelrate(struct v4l2_subdev *sd, s64 rate)
{
struct adv748x_csi2 *tx = adv748x_sd_to_csi2(sd);
if (!tx->pixel_rate)
return -EINVAL;
return v4l2_ctrl_s_ctrl_int64(tx->pixel_rate, rate);
}
static int adv748x_csi2_s_ctrl(struct v4l2_ctrl *ctrl)
{
switch (ctrl->id) {
case V4L2_CID_PIXEL_RATE:
return 0;
default:
return -EINVAL;
}
}
static const struct v4l2_ctrl_ops adv748x_csi2_ctrl_ops = {
.s_ctrl = adv748x_csi2_s_ctrl,
};
static int adv748x_csi2_init_controls(struct adv748x_csi2 *tx)
{
v4l2_ctrl_handler_init(&tx->ctrl_hdl, 1);
tx->pixel_rate = v4l2_ctrl_new_std(&tx->ctrl_hdl,
&adv748x_csi2_ctrl_ops,
V4L2_CID_PIXEL_RATE, 1, INT_MAX,
1, 1);
tx->sd.ctrl_handler = &tx->ctrl_hdl;
if (tx->ctrl_hdl.error) {
v4l2_ctrl_handler_free(&tx->ctrl_hdl);
return tx->ctrl_hdl.error;
}
return v4l2_ctrl_handler_setup(&tx->ctrl_hdl);
}
int adv748x_csi2_init(struct adv748x_state *state, struct adv748x_csi2 *tx)
{
int ret;
if (!is_tx_enabled(tx))
return 0;
adv748x_subdev_init(&tx->sd, state, &adv748x_csi2_ops,
MEDIA_ENT_F_VID_IF_BRIDGE,
is_txa(tx) ? "txa" : "txb");
/* Register internal ops for incremental subdev registration */
tx->sd.internal_ops = &adv748x_csi2_internal_ops;
tx->pads[ADV748X_CSI2_SINK].flags = MEDIA_PAD_FL_SINK;
tx->pads[ADV748X_CSI2_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
ret = media_entity_pads_init(&tx->sd.entity, ADV748X_CSI2_NR_PADS,
tx->pads);
if (ret)
return ret;
ret = v4l2_async_subdev_endpoint_add(&tx->sd,
of_fwnode_handle(state->endpoints[tx->port]));
if (ret)
goto err_free_media;
ret = adv748x_csi2_init_controls(tx);
if (ret)
goto err_cleanup_subdev;
tx->sd.state_lock = &state->mutex;
ret = v4l2_subdev_init_finalize(&tx->sd);
if (ret)
goto err_free_ctrl;
ret = v4l2_async_register_subdev(&tx->sd);
if (ret)
goto err_free_ctrl;
return 0;
err_free_ctrl:
v4l2_ctrl_handler_free(&tx->ctrl_hdl);
err_cleanup_subdev:
v4l2_subdev_cleanup(&tx->sd);
err_free_media:
media_entity_cleanup(&tx->sd.entity);
return ret;
}
void adv748x_csi2_cleanup(struct adv748x_csi2 *tx)
{
if (!is_tx_enabled(tx))
return;
v4l2_async_unregister_subdev(&tx->sd);
media_entity_cleanup(&tx->sd.entity);
v4l2_ctrl_handler_free(&tx->ctrl_hdl);
v4l2_subdev_cleanup(&tx->sd);
}
@@ -0,0 +1,761 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Driver for Analog Devices ADV748X HDMI receiver and Component Processor (CP)
*
* Copyright (C) 2017 Renesas Electronics Corp.
*/
#include <linux/module.h>
#include <linux/mutex.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-device.h>
#include <media/v4l2-dv-timings.h>
#include <media/v4l2-ioctl.h>
#include <uapi/linux/v4l2-dv-timings.h>
#include "adv748x.h"
/* -----------------------------------------------------------------------------
* HDMI and CP
*/
#define ADV748X_HDMI_MIN_WIDTH 640
#define ADV748X_HDMI_MAX_WIDTH 1920
#define ADV748X_HDMI_MIN_HEIGHT 480
#define ADV748X_HDMI_MAX_HEIGHT 1200
/* V4L2_DV_BT_CEA_720X480I59_94 - 0.5 MHz */
#define ADV748X_HDMI_MIN_PIXELCLOCK 13000000
/* V4L2_DV_BT_DMT_1600X1200P60 */
#define ADV748X_HDMI_MAX_PIXELCLOCK 162000000
static const struct v4l2_dv_timings_cap adv748x_hdmi_timings_cap = {
.type = V4L2_DV_BT_656_1120,
/* keep this initialization for compatibility with GCC < 4.4.6 */
.reserved = { 0 },
V4L2_INIT_BT_TIMINGS(ADV748X_HDMI_MIN_WIDTH, ADV748X_HDMI_MAX_WIDTH,
ADV748X_HDMI_MIN_HEIGHT, ADV748X_HDMI_MAX_HEIGHT,
ADV748X_HDMI_MIN_PIXELCLOCK,
ADV748X_HDMI_MAX_PIXELCLOCK,
V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT,
V4L2_DV_BT_CAP_PROGRESSIVE)
};
struct adv748x_hdmi_video_standards {
struct v4l2_dv_timings timings;
u8 vid_std;
u8 v_freq;
};
static const struct adv748x_hdmi_video_standards
adv748x_hdmi_video_standards[] = {
{ V4L2_DV_BT_CEA_720X480P59_94, 0x4a, 0x00 },
{ V4L2_DV_BT_CEA_720X576P50, 0x4b, 0x00 },
{ V4L2_DV_BT_CEA_1280X720P60, 0x53, 0x00 },
{ V4L2_DV_BT_CEA_1280X720P50, 0x53, 0x01 },
{ V4L2_DV_BT_CEA_1280X720P30, 0x53, 0x02 },
{ V4L2_DV_BT_CEA_1280X720P25, 0x53, 0x03 },
{ V4L2_DV_BT_CEA_1280X720P24, 0x53, 0x04 },
{ V4L2_DV_BT_CEA_1920X1080P60, 0x5e, 0x00 },
{ V4L2_DV_BT_CEA_1920X1080P50, 0x5e, 0x01 },
{ V4L2_DV_BT_CEA_1920X1080P30, 0x5e, 0x02 },
{ V4L2_DV_BT_CEA_1920X1080P25, 0x5e, 0x03 },
{ V4L2_DV_BT_CEA_1920X1080P24, 0x5e, 0x04 },
/* SVGA */
{ V4L2_DV_BT_DMT_800X600P56, 0x80, 0x00 },
{ V4L2_DV_BT_DMT_800X600P60, 0x81, 0x00 },
{ V4L2_DV_BT_DMT_800X600P72, 0x82, 0x00 },
{ V4L2_DV_BT_DMT_800X600P75, 0x83, 0x00 },
{ V4L2_DV_BT_DMT_800X600P85, 0x84, 0x00 },
/* SXGA */
{ V4L2_DV_BT_DMT_1280X1024P60, 0x85, 0x00 },
{ V4L2_DV_BT_DMT_1280X1024P75, 0x86, 0x00 },
/* VGA */
{ V4L2_DV_BT_DMT_640X480P60, 0x88, 0x00 },
{ V4L2_DV_BT_DMT_640X480P72, 0x89, 0x00 },
{ V4L2_DV_BT_DMT_640X480P75, 0x8a, 0x00 },
{ V4L2_DV_BT_DMT_640X480P85, 0x8b, 0x00 },
/* XGA */
{ V4L2_DV_BT_DMT_1024X768P60, 0x8c, 0x00 },
{ V4L2_DV_BT_DMT_1024X768P70, 0x8d, 0x00 },
{ V4L2_DV_BT_DMT_1024X768P75, 0x8e, 0x00 },
{ V4L2_DV_BT_DMT_1024X768P85, 0x8f, 0x00 },
/* UXGA */
{ V4L2_DV_BT_DMT_1600X1200P60, 0x96, 0x00 },
};
static void adv748x_hdmi_fill_format(struct adv748x_hdmi *hdmi,
struct v4l2_mbus_framefmt *fmt)
{
memset(fmt, 0, sizeof(*fmt));
fmt->code = MEDIA_BUS_FMT_RGB888_1X24;
fmt->field = hdmi->timings.bt.interlaced ?
V4L2_FIELD_ALTERNATE : V4L2_FIELD_NONE;
/* TODO: The colorspace depends on the AVI InfoFrame contents */
fmt->colorspace = V4L2_COLORSPACE_SRGB;
fmt->width = hdmi->timings.bt.width;
fmt->height = hdmi->timings.bt.height;
if (fmt->field == V4L2_FIELD_ALTERNATE)
fmt->height /= 2;
}
static void adv748x_fill_optional_dv_timings(struct v4l2_dv_timings *timings)
{
v4l2_find_dv_timings_cap(timings, &adv748x_hdmi_timings_cap,
250000, NULL, NULL);
}
static bool adv748x_hdmi_has_signal(struct adv748x_state *state)
{
int val;
/* Check that VERT_FILTER and DE_REGEN is locked */
val = hdmi_read(state, ADV748X_HDMI_LW1);
return (val & ADV748X_HDMI_LW1_VERT_FILTER) &&
(val & ADV748X_HDMI_LW1_DE_REGEN);
}
static int adv748x_hdmi_read_pixelclock(struct adv748x_state *state)
{
int a, b;
a = hdmi_read(state, ADV748X_HDMI_TMDS_1);
b = hdmi_read(state, ADV748X_HDMI_TMDS_2);
if (a < 0 || b < 0)
return -ENODATA;
/*
* The high 9 bits store TMDS frequency measurement in MHz
* The low 7 bits of TMDS_2 store the 7-bit TMDS fractional frequency
* measurement in 1/128 MHz
*/
return ((a << 1) | (b >> 7)) * 1000000 + (b & 0x7f) * 1000000 / 128;
}
/*
* adv748x_hdmi_set_de_timings: Adjust horizontal picture offset through DE
*
* HDMI CP uses a Data Enable synchronisation timing reference
*
* Vary the leading and trailing edge position of the DE signal output by the CP
* core. Values are stored as signed-twos-complement in one-pixel-clock units
*
* The start and end are shifted equally by the 10-bit shift value.
*/
static void adv748x_hdmi_set_de_timings(struct adv748x_state *state, int shift)
{
u8 high, low;
/* POS_HIGH stores bits 8 and 9 of both the start and end */
high = ADV748X_CP_DE_POS_HIGH_SET;
high |= (shift & 0x300) >> 8;
low = shift & 0xff;
/* The sequence of the writes is important and must be followed */
cp_write(state, ADV748X_CP_DE_POS_HIGH, high);
cp_write(state, ADV748X_CP_DE_POS_END_LOW, low);
high |= (shift & 0x300) >> 6;
cp_write(state, ADV748X_CP_DE_POS_HIGH, high);
cp_write(state, ADV748X_CP_DE_POS_START_LOW, low);
}
static int adv748x_hdmi_set_video_timings(struct adv748x_state *state,
const struct v4l2_dv_timings *timings)
{
const struct adv748x_hdmi_video_standards *stds =
adv748x_hdmi_video_standards;
unsigned int i;
for (i = 0; i < ARRAY_SIZE(adv748x_hdmi_video_standards); i++) {
if (v4l2_match_dv_timings(timings, &stds[i].timings, 250000,
false))
break;
}
if (i >= ARRAY_SIZE(adv748x_hdmi_video_standards))
return -EINVAL;
/*
* When setting cp_vid_std to either 720p, 1080i, or 1080p, the video
* will get shifted horizontally to the left in active video mode.
* The de_h_start and de_h_end controls are used to centre the picture
* correctly
*/
switch (stds[i].vid_std) {
case 0x53: /* 720p */
adv748x_hdmi_set_de_timings(state, -40);
break;
case 0x54: /* 1080i */
case 0x5e: /* 1080p */
adv748x_hdmi_set_de_timings(state, -44);
break;
default:
adv748x_hdmi_set_de_timings(state, 0);
break;
}
io_write(state, ADV748X_IO_VID_STD, stds[i].vid_std);
io_clrset(state, ADV748X_IO_DATAPATH, ADV748X_IO_DATAPATH_VFREQ_M,
stds[i].v_freq << ADV748X_IO_DATAPATH_VFREQ_SHIFT);
return 0;
}
/* -----------------------------------------------------------------------------
* v4l2_subdev_video_ops
*/
static int adv748x_hdmi_s_dv_timings(struct v4l2_subdev *sd, unsigned int pad,
struct v4l2_dv_timings *timings)
{
struct adv748x_hdmi *hdmi = adv748x_sd_to_hdmi(sd);
struct adv748x_state *state = adv748x_hdmi_to_state(hdmi);
int ret;
if (!timings)
return -EINVAL;
if (v4l2_match_dv_timings(&hdmi->timings, timings, 0, false))
return 0;
if (!v4l2_valid_dv_timings(timings, &adv748x_hdmi_timings_cap,
NULL, NULL))
return -ERANGE;
adv748x_fill_optional_dv_timings(timings);
mutex_lock(&state->mutex);
ret = adv748x_hdmi_set_video_timings(state, timings);
if (ret)
goto error;
hdmi->timings = *timings;
cp_clrset(state, ADV748X_CP_VID_ADJ_2, ADV748X_CP_VID_ADJ_2_INTERLACED,
timings->bt.interlaced ?
ADV748X_CP_VID_ADJ_2_INTERLACED : 0);
mutex_unlock(&state->mutex);
return 0;
error:
mutex_unlock(&state->mutex);
return ret;
}
static int adv748x_hdmi_g_dv_timings(struct v4l2_subdev *sd, unsigned int pad,
struct v4l2_dv_timings *timings)
{
struct adv748x_hdmi *hdmi = adv748x_sd_to_hdmi(sd);
struct adv748x_state *state = adv748x_hdmi_to_state(hdmi);
mutex_lock(&state->mutex);
*timings = hdmi->timings;
mutex_unlock(&state->mutex);
return 0;
}
static int adv748x_hdmi_query_dv_timings(struct v4l2_subdev *sd, unsigned int pad,
struct v4l2_dv_timings *timings)
{
struct adv748x_hdmi *hdmi = adv748x_sd_to_hdmi(sd);
struct adv748x_state *state = adv748x_hdmi_to_state(hdmi);
struct v4l2_bt_timings *bt = &timings->bt;
int pixelclock;
int polarity;
if (!timings)
return -EINVAL;
memset(timings, 0, sizeof(struct v4l2_dv_timings));
/*
* If the pattern generator is enabled the device shall not be queried
* for timings. Instead the timings programmed shall be reported as they
* are the ones being used to generate the pattern.
*/
if (cp_read(state, ADV748X_CP_PAT_GEN) & ADV748X_CP_PAT_GEN_EN) {
*timings = hdmi->timings;
return 0;
}
if (!adv748x_hdmi_has_signal(state))
return -ENOLINK;
pixelclock = adv748x_hdmi_read_pixelclock(state);
if (pixelclock < 0)
return -ENODATA;
timings->type = V4L2_DV_BT_656_1120;
bt->pixelclock = pixelclock;
bt->interlaced = hdmi_read(state, ADV748X_HDMI_F1H1) &
ADV748X_HDMI_F1H1_INTERLACED ?
V4L2_DV_INTERLACED : V4L2_DV_PROGRESSIVE;
bt->width = hdmi_read16(state, ADV748X_HDMI_LW1,
ADV748X_HDMI_LW1_WIDTH_MASK);
bt->height = hdmi_read16(state, ADV748X_HDMI_F0H1,
ADV748X_HDMI_F0H1_HEIGHT_MASK);
bt->hfrontporch = hdmi_read16(state, ADV748X_HDMI_HFRONT_PORCH,
ADV748X_HDMI_HFRONT_PORCH_MASK);
bt->hsync = hdmi_read16(state, ADV748X_HDMI_HSYNC_WIDTH,
ADV748X_HDMI_HSYNC_WIDTH_MASK);
bt->hbackporch = hdmi_read16(state, ADV748X_HDMI_HBACK_PORCH,
ADV748X_HDMI_HBACK_PORCH_MASK);
bt->vfrontporch = hdmi_read16(state, ADV748X_HDMI_VFRONT_PORCH,
ADV748X_HDMI_VFRONT_PORCH_MASK) / 2;
bt->vsync = hdmi_read16(state, ADV748X_HDMI_VSYNC_WIDTH,
ADV748X_HDMI_VSYNC_WIDTH_MASK) / 2;
bt->vbackporch = hdmi_read16(state, ADV748X_HDMI_VBACK_PORCH,
ADV748X_HDMI_VBACK_PORCH_MASK) / 2;
polarity = hdmi_read(state, 0x05);
bt->polarities = (polarity & BIT(4) ? V4L2_DV_VSYNC_POS_POL : 0) |
(polarity & BIT(5) ? V4L2_DV_HSYNC_POS_POL : 0);
if (bt->interlaced == V4L2_DV_INTERLACED) {
bt->height += hdmi_read16(state, 0x0b, 0x1fff);
bt->il_vfrontporch = hdmi_read16(state, 0x2c, 0x3fff) / 2;
bt->il_vsync = hdmi_read16(state, 0x30, 0x3fff) / 2;
bt->il_vbackporch = hdmi_read16(state, 0x34, 0x3fff) / 2;
}
adv748x_fill_optional_dv_timings(timings);
/*
* No interrupt handling is implemented yet.
* There should be an IRQ when a cable is plugged and the new timings
* should be figured out and stored to state.
*/
hdmi->timings = *timings;
return 0;
}
static int adv748x_hdmi_g_input_status(struct v4l2_subdev *sd, u32 *status)
{
struct adv748x_hdmi *hdmi = adv748x_sd_to_hdmi(sd);
struct adv748x_state *state = adv748x_hdmi_to_state(hdmi);
mutex_lock(&state->mutex);
*status = adv748x_hdmi_has_signal(state) ? 0 : V4L2_IN_ST_NO_SIGNAL;
mutex_unlock(&state->mutex);
return 0;
}
static int adv748x_hdmi_s_stream(struct v4l2_subdev *sd, int enable)
{
struct adv748x_hdmi *hdmi = adv748x_sd_to_hdmi(sd);
struct adv748x_state *state = adv748x_hdmi_to_state(hdmi);
int ret;
mutex_lock(&state->mutex);
ret = adv748x_tx_power(hdmi->tx, enable);
if (ret)
goto done;
if (adv748x_hdmi_has_signal(state))
adv_dbg(state, "Detected HDMI signal\n");
else
adv_dbg(state, "Couldn't detect HDMI video signal\n");
done:
mutex_unlock(&state->mutex);
return ret;
}
static const struct v4l2_subdev_video_ops adv748x_video_ops_hdmi = {
.g_input_status = adv748x_hdmi_g_input_status,
.s_stream = adv748x_hdmi_s_stream,
};
/* -----------------------------------------------------------------------------
* v4l2_subdev_pad_ops
*/
static int adv748x_hdmi_propagate_pixelrate(struct adv748x_hdmi *hdmi)
{
struct v4l2_subdev *tx;
struct v4l2_dv_timings timings;
tx = adv748x_get_remote_sd(&hdmi->pads[ADV748X_HDMI_SOURCE]);
if (!tx)
return -ENOLINK;
adv748x_hdmi_query_dv_timings(&hdmi->sd, 0, &timings);
return adv748x_csi2_set_pixelrate(tx, timings.bt.pixelclock);
}
static int adv748x_hdmi_enum_mbus_code(struct v4l2_subdev *sd,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_mbus_code_enum *code)
{
if (code->index != 0)
return -EINVAL;
code->code = MEDIA_BUS_FMT_RGB888_1X24;
return 0;
}
static int adv748x_hdmi_get_format(struct v4l2_subdev *sd,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_format *sdformat)
{
struct adv748x_hdmi *hdmi = adv748x_sd_to_hdmi(sd);
struct v4l2_mbus_framefmt *mbusformat;
if (sdformat->pad != ADV748X_HDMI_SOURCE)
return -EINVAL;
if (sdformat->which == V4L2_SUBDEV_FORMAT_TRY) {
mbusformat = v4l2_subdev_state_get_format(sd_state,
sdformat->pad);
sdformat->format = *mbusformat;
} else {
adv748x_hdmi_fill_format(hdmi, &sdformat->format);
adv748x_hdmi_propagate_pixelrate(hdmi);
}
return 0;
}
static int adv748x_hdmi_set_format(struct v4l2_subdev *sd,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_format *sdformat)
{
struct v4l2_mbus_framefmt *mbusformat;
if (sdformat->pad != ADV748X_HDMI_SOURCE)
return -EINVAL;
if (sdformat->which == V4L2_SUBDEV_FORMAT_ACTIVE)
return adv748x_hdmi_get_format(sd, sd_state, sdformat);
mbusformat = v4l2_subdev_state_get_format(sd_state, sdformat->pad);
*mbusformat = sdformat->format;
return 0;
}
static int adv748x_hdmi_get_edid(struct v4l2_subdev *sd, struct v4l2_edid *edid)
{
struct adv748x_hdmi *hdmi = adv748x_sd_to_hdmi(sd);
memset(edid->reserved, 0, sizeof(edid->reserved));
if (!hdmi->edid.present)
return -ENODATA;
if (edid->start_block == 0 && edid->blocks == 0) {
edid->blocks = hdmi->edid.blocks;
return 0;
}
if (edid->start_block >= hdmi->edid.blocks)
return -EINVAL;
if (edid->start_block + edid->blocks > hdmi->edid.blocks)
edid->blocks = hdmi->edid.blocks - edid->start_block;
memcpy(edid->edid, hdmi->edid.edid + edid->start_block * 128,
edid->blocks * 128);
return 0;
}
static inline int adv748x_hdmi_edid_write_block(struct adv748x_hdmi *hdmi,
unsigned int total_len, const u8 *val)
{
struct adv748x_state *state = adv748x_hdmi_to_state(hdmi);
int err = 0;
int i = 0;
int len = 0;
adv_dbg(state, "%s: write EDID block (%d byte)\n",
__func__, total_len);
while (!err && i < total_len) {
len = (total_len - i) > I2C_SMBUS_BLOCK_MAX ?
I2C_SMBUS_BLOCK_MAX :
(total_len - i);
err = adv748x_write_block(state, ADV748X_PAGE_EDID,
i, val + i, len);
i += len;
}
return err;
}
static int adv748x_hdmi_set_edid(struct v4l2_subdev *sd, struct v4l2_edid *edid)
{
struct adv748x_hdmi *hdmi = adv748x_sd_to_hdmi(sd);
struct adv748x_state *state = adv748x_hdmi_to_state(hdmi);
int err;
memset(edid->reserved, 0, sizeof(edid->reserved));
if (edid->start_block != 0)
return -EINVAL;
if (edid->blocks == 0) {
hdmi->edid.blocks = 0;
hdmi->edid.present = 0;
/* Fall back to a 16:9 aspect ratio */
hdmi->aspect_ratio.numerator = 16;
hdmi->aspect_ratio.denominator = 9;
/* Disable the EDID */
repeater_write(state, ADV748X_REPEATER_EDID_SZ,
edid->blocks << ADV748X_REPEATER_EDID_SZ_SHIFT);
repeater_write(state, ADV748X_REPEATER_EDID_CTL, 0);
return 0;
}
if (edid->blocks > 4) {
edid->blocks = 4;
return -E2BIG;
}
memcpy(hdmi->edid.edid, edid->edid, 128 * edid->blocks);
hdmi->edid.blocks = edid->blocks;
hdmi->edid.present = true;
hdmi->aspect_ratio = v4l2_calc_aspect_ratio(edid->edid[0x15],
edid->edid[0x16]);
err = adv748x_hdmi_edid_write_block(hdmi, 128 * edid->blocks,
hdmi->edid.edid);
if (err < 0) {
v4l2_err(sd, "error %d writing edid pad %d\n", err, edid->pad);
return err;
}
repeater_write(state, ADV748X_REPEATER_EDID_SZ,
edid->blocks << ADV748X_REPEATER_EDID_SZ_SHIFT);
repeater_write(state, ADV748X_REPEATER_EDID_CTL,
ADV748X_REPEATER_EDID_CTL_EN);
return 0;
}
static bool adv748x_hdmi_check_dv_timings(const struct v4l2_dv_timings *timings,
void *hdl)
{
const struct adv748x_hdmi_video_standards *stds =
adv748x_hdmi_video_standards;
unsigned int i;
for (i = 0; stds[i].timings.bt.width; i++)
if (v4l2_match_dv_timings(timings, &stds[i].timings, 0, false))
return true;
return false;
}
static int adv748x_hdmi_enum_dv_timings(struct v4l2_subdev *sd,
struct v4l2_enum_dv_timings *timings)
{
return v4l2_enum_dv_timings_cap(timings, &adv748x_hdmi_timings_cap,
adv748x_hdmi_check_dv_timings, NULL);
}
static int adv748x_hdmi_dv_timings_cap(struct v4l2_subdev *sd,
struct v4l2_dv_timings_cap *cap)
{
*cap = adv748x_hdmi_timings_cap;
return 0;
}
static const struct v4l2_subdev_pad_ops adv748x_pad_ops_hdmi = {
.enum_mbus_code = adv748x_hdmi_enum_mbus_code,
.set_fmt = adv748x_hdmi_set_format,
.get_fmt = adv748x_hdmi_get_format,
.get_edid = adv748x_hdmi_get_edid,
.set_edid = adv748x_hdmi_set_edid,
.s_dv_timings = adv748x_hdmi_s_dv_timings,
.g_dv_timings = adv748x_hdmi_g_dv_timings,
.query_dv_timings = adv748x_hdmi_query_dv_timings,
.dv_timings_cap = adv748x_hdmi_dv_timings_cap,
.enum_dv_timings = adv748x_hdmi_enum_dv_timings,
};
/* -----------------------------------------------------------------------------
* v4l2_subdev_ops
*/
static const struct v4l2_subdev_ops adv748x_ops_hdmi = {
.video = &adv748x_video_ops_hdmi,
.pad = &adv748x_pad_ops_hdmi,
};
/* -----------------------------------------------------------------------------
* Controls
*/
static const char * const hdmi_ctrl_patgen_menu[] = {
"Disabled",
"Solid Color",
"Color Bars",
"Ramp Grey",
"Ramp Blue",
"Ramp Red",
"Checkered"
};
static int adv748x_hdmi_s_ctrl(struct v4l2_ctrl *ctrl)
{
struct adv748x_hdmi *hdmi = adv748x_ctrl_to_hdmi(ctrl);
struct adv748x_state *state = adv748x_hdmi_to_state(hdmi);
int ret;
u8 pattern;
/* Enable video adjustment first */
ret = cp_clrset(state, ADV748X_CP_VID_ADJ,
ADV748X_CP_VID_ADJ_ENABLE,
ADV748X_CP_VID_ADJ_ENABLE);
if (ret < 0)
return ret;
switch (ctrl->id) {
case V4L2_CID_BRIGHTNESS:
ret = cp_write(state, ADV748X_CP_BRI, ctrl->val);
break;
case V4L2_CID_HUE:
ret = cp_write(state, ADV748X_CP_HUE, ctrl->val);
break;
case V4L2_CID_CONTRAST:
ret = cp_write(state, ADV748X_CP_CON, ctrl->val);
break;
case V4L2_CID_SATURATION:
ret = cp_write(state, ADV748X_CP_SAT, ctrl->val);
break;
case V4L2_CID_TEST_PATTERN:
pattern = ctrl->val;
/* Pattern is 0-indexed. Ctrl Menu is 1-indexed */
if (pattern) {
pattern--;
pattern |= ADV748X_CP_PAT_GEN_EN;
}
ret = cp_write(state, ADV748X_CP_PAT_GEN, pattern);
break;
default:
return -EINVAL;
}
return ret;
}
static const struct v4l2_ctrl_ops adv748x_hdmi_ctrl_ops = {
.s_ctrl = adv748x_hdmi_s_ctrl,
};
static int adv748x_hdmi_init_controls(struct adv748x_hdmi *hdmi)
{
struct adv748x_state *state = adv748x_hdmi_to_state(hdmi);
v4l2_ctrl_handler_init(&hdmi->ctrl_hdl, 5);
/* Use our mutex for the controls */
hdmi->ctrl_hdl.lock = &state->mutex;
v4l2_ctrl_new_std(&hdmi->ctrl_hdl, &adv748x_hdmi_ctrl_ops,
V4L2_CID_BRIGHTNESS, ADV748X_CP_BRI_MIN,
ADV748X_CP_BRI_MAX, 1, ADV748X_CP_BRI_DEF);
v4l2_ctrl_new_std(&hdmi->ctrl_hdl, &adv748x_hdmi_ctrl_ops,
V4L2_CID_CONTRAST, ADV748X_CP_CON_MIN,
ADV748X_CP_CON_MAX, 1, ADV748X_CP_CON_DEF);
v4l2_ctrl_new_std(&hdmi->ctrl_hdl, &adv748x_hdmi_ctrl_ops,
V4L2_CID_SATURATION, ADV748X_CP_SAT_MIN,
ADV748X_CP_SAT_MAX, 1, ADV748X_CP_SAT_DEF);
v4l2_ctrl_new_std(&hdmi->ctrl_hdl, &adv748x_hdmi_ctrl_ops,
V4L2_CID_HUE, ADV748X_CP_HUE_MIN,
ADV748X_CP_HUE_MAX, 1, ADV748X_CP_HUE_DEF);
/*
* Todo: V4L2_CID_DV_RX_POWER_PRESENT should also be supported when
* interrupts are handled correctly
*/
v4l2_ctrl_new_std_menu_items(&hdmi->ctrl_hdl, &adv748x_hdmi_ctrl_ops,
V4L2_CID_TEST_PATTERN,
ARRAY_SIZE(hdmi_ctrl_patgen_menu) - 1,
0, 0, hdmi_ctrl_patgen_menu);
hdmi->sd.ctrl_handler = &hdmi->ctrl_hdl;
if (hdmi->ctrl_hdl.error) {
v4l2_ctrl_handler_free(&hdmi->ctrl_hdl);
return hdmi->ctrl_hdl.error;
}
return v4l2_ctrl_handler_setup(&hdmi->ctrl_hdl);
}
int adv748x_hdmi_init(struct adv748x_hdmi *hdmi)
{
struct adv748x_state *state = adv748x_hdmi_to_state(hdmi);
struct v4l2_dv_timings cea1280x720 = V4L2_DV_BT_CEA_1280X720P30;
int ret;
adv748x_hdmi_s_dv_timings(&hdmi->sd, 0, &cea1280x720);
/* Initialise a default 16:9 aspect ratio */
hdmi->aspect_ratio.numerator = 16;
hdmi->aspect_ratio.denominator = 9;
adv748x_subdev_init(&hdmi->sd, state, &adv748x_ops_hdmi,
MEDIA_ENT_F_IO_DTV, "hdmi");
hdmi->pads[ADV748X_HDMI_SINK].flags = MEDIA_PAD_FL_SINK;
hdmi->pads[ADV748X_HDMI_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
ret = media_entity_pads_init(&hdmi->sd.entity,
ADV748X_HDMI_NR_PADS, hdmi->pads);
if (ret)
return ret;
ret = adv748x_hdmi_init_controls(hdmi);
if (ret)
goto err_free_media;
return 0;
err_free_media:
media_entity_cleanup(&hdmi->sd.entity);
return ret;
}
void adv748x_hdmi_cleanup(struct adv748x_hdmi *hdmi)
{
v4l2_device_unregister_subdev(&hdmi->sd);
media_entity_cleanup(&hdmi->sd.entity);
v4l2_ctrl_handler_free(&hdmi->ctrl_hdl);
}
@@ -0,0 +1,443 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Driver for Analog Devices ADV748X video decoder and HDMI receiver
*
* Copyright (C) 2017 Renesas Electronics Corp.
*
* Authors:
* Koji Matsuoka <koji.matsuoka.xm@renesas.com>
* Niklas Söderlund <niklas.soderlund@ragnatech.se>
* Kieran Bingham <kieran.bingham@ideasonboard.com>
*
* The ADV748x range of receivers have the following configurations:
*
* Analog HDMI MHL 4-Lane 1-Lane
* In In CSI CSI
* ADV7480 X X X
* ADV7481 X X X X X
* ADV7482 X X X X
*/
#include <linux/i2c.h>
#ifndef _ADV748X_H_
#define _ADV748X_H_
enum adv748x_page {
ADV748X_PAGE_IO,
ADV748X_PAGE_DPLL,
ADV748X_PAGE_CP,
ADV748X_PAGE_HDMI,
ADV748X_PAGE_EDID,
ADV748X_PAGE_REPEATER,
ADV748X_PAGE_INFOFRAME,
ADV748X_PAGE_CBUS,
ADV748X_PAGE_CEC,
ADV748X_PAGE_SDP,
ADV748X_PAGE_TXB,
ADV748X_PAGE_TXA,
ADV748X_PAGE_MAX,
/* Fake pages for register sequences */
ADV748X_PAGE_EOR, /* End Mark */
};
/*
* Device tree port number definitions
*
* The ADV748X ports define the mapping between subdevices
* and the device tree specification
*/
enum adv748x_ports {
ADV748X_PORT_AIN0 = 0,
ADV748X_PORT_AIN1 = 1,
ADV748X_PORT_AIN2 = 2,
ADV748X_PORT_AIN3 = 3,
ADV748X_PORT_AIN4 = 4,
ADV748X_PORT_AIN5 = 5,
ADV748X_PORT_AIN6 = 6,
ADV748X_PORT_AIN7 = 7,
ADV748X_PORT_HDMI = 8,
ADV748X_PORT_TTL = 9,
ADV748X_PORT_TXA = 10,
ADV748X_PORT_TXB = 11,
ADV748X_PORT_MAX = 12,
};
enum adv748x_csi2_pads {
ADV748X_CSI2_SINK,
ADV748X_CSI2_SOURCE,
ADV748X_CSI2_NR_PADS,
};
/* CSI2 transmitters can have 2 internal connections, HDMI/AFE */
#define ADV748X_CSI2_MAX_SUBDEVS 2
struct adv748x_csi2 {
struct adv748x_state *state;
unsigned int page;
unsigned int port;
unsigned int num_lanes;
unsigned int active_lanes;
struct media_pad pads[ADV748X_CSI2_NR_PADS];
struct v4l2_ctrl_handler ctrl_hdl;
struct v4l2_ctrl *pixel_rate;
struct v4l2_subdev *src;
struct v4l2_subdev sd;
};
#define notifier_to_csi2(n) container_of(n, struct adv748x_csi2, notifier)
#define adv748x_sd_to_csi2(sd) container_of(sd, struct adv748x_csi2, sd)
#define is_tx_enabled(_tx) ((_tx)->state->endpoints[(_tx)->port] != NULL)
#define is_txa(_tx) ((_tx) == &(_tx)->state->txa)
#define is_txb(_tx) ((_tx) == &(_tx)->state->txb)
#define is_tx(_tx) (is_txa(_tx) || is_txb(_tx))
#define is_afe_enabled(_state) \
((_state)->endpoints[ADV748X_PORT_AIN0] != NULL || \
(_state)->endpoints[ADV748X_PORT_AIN1] != NULL || \
(_state)->endpoints[ADV748X_PORT_AIN2] != NULL || \
(_state)->endpoints[ADV748X_PORT_AIN3] != NULL || \
(_state)->endpoints[ADV748X_PORT_AIN4] != NULL || \
(_state)->endpoints[ADV748X_PORT_AIN5] != NULL || \
(_state)->endpoints[ADV748X_PORT_AIN6] != NULL || \
(_state)->endpoints[ADV748X_PORT_AIN7] != NULL)
#define is_hdmi_enabled(_state) ((_state)->endpoints[ADV748X_PORT_HDMI] != NULL)
enum adv748x_hdmi_pads {
ADV748X_HDMI_SINK,
ADV748X_HDMI_SOURCE,
ADV748X_HDMI_NR_PADS,
};
struct adv748x_hdmi {
struct media_pad pads[ADV748X_HDMI_NR_PADS];
struct v4l2_ctrl_handler ctrl_hdl;
struct v4l2_subdev sd;
struct v4l2_mbus_framefmt format;
struct v4l2_dv_timings timings;
struct v4l2_fract aspect_ratio;
struct adv748x_csi2 *tx;
struct {
u8 edid[512];
u32 present;
unsigned int blocks;
} edid;
};
#define adv748x_ctrl_to_hdmi(ctrl) \
container_of(ctrl->handler, struct adv748x_hdmi, ctrl_hdl)
#define adv748x_sd_to_hdmi(sd) container_of(sd, struct adv748x_hdmi, sd)
enum adv748x_afe_pads {
ADV748X_AFE_SINK_AIN0,
ADV748X_AFE_SINK_AIN1,
ADV748X_AFE_SINK_AIN2,
ADV748X_AFE_SINK_AIN3,
ADV748X_AFE_SINK_AIN4,
ADV748X_AFE_SINK_AIN5,
ADV748X_AFE_SINK_AIN6,
ADV748X_AFE_SINK_AIN7,
ADV748X_AFE_SOURCE,
ADV748X_AFE_NR_PADS,
};
struct adv748x_afe {
struct media_pad pads[ADV748X_AFE_NR_PADS];
struct v4l2_ctrl_handler ctrl_hdl;
struct v4l2_subdev sd;
struct v4l2_mbus_framefmt format;
struct adv748x_csi2 *tx;
bool streaming;
v4l2_std_id curr_norm;
unsigned int input;
};
#define adv748x_ctrl_to_afe(ctrl) \
container_of(ctrl->handler, struct adv748x_afe, ctrl_hdl)
#define adv748x_sd_to_afe(sd) container_of(sd, struct adv748x_afe, sd)
/**
* struct adv748x_state - State of ADV748X
* @dev: (OF) device
* @client: I2C client
* @mutex: protect global state
*
* @endpoints: parsed device node endpoints for each port
*
* @i2c_clients: I2C clients for the page accesses
* @regmap: regmap configuration pages.
*
* @hdmi: state of HDMI receiver context
* @afe: state of AFE receiver context
* @txa: state of TXA transmitter context
* @txb: state of TXB transmitter context
*/
struct adv748x_state {
struct device *dev;
struct i2c_client *client;
struct mutex mutex;
struct device_node *endpoints[ADV748X_PORT_MAX];
struct i2c_client *i2c_clients[ADV748X_PAGE_MAX];
struct regmap *regmap[ADV748X_PAGE_MAX];
struct adv748x_hdmi hdmi;
struct adv748x_afe afe;
struct adv748x_csi2 txa;
struct adv748x_csi2 txb;
};
#define adv748x_hdmi_to_state(h) container_of(h, struct adv748x_state, hdmi)
#define adv748x_afe_to_state(a) container_of(a, struct adv748x_state, afe)
#define adv_err(a, fmt, arg...) dev_err(a->dev, fmt, ##arg)
#define adv_info(a, fmt, arg...) dev_info(a->dev, fmt, ##arg)
#define adv_dbg(a, fmt, arg...) dev_dbg(a->dev, fmt, ##arg)
/* Register Mappings */
/* IO Map */
#define ADV748X_IO_PD 0x00 /* power down controls */
#define ADV748X_IO_PD_RX_EN BIT(6)
#define ADV748X_IO_REG_01 0x01 /* pwrdn{2}b, prog_xtal_freq */
#define ADV748X_IO_REG_01_PWRDN_MASK (BIT(7) | BIT(6))
#define ADV748X_IO_REG_01_PWRDN2B BIT(7) /* CEC Wakeup Support */
#define ADV748X_IO_REG_01_PWRDNB BIT(6) /* CEC Wakeup Support */
#define ADV748X_IO_REG_04 0x04
#define ADV748X_IO_REG_04_FORCE_FR BIT(0) /* Force CP free-run */
#define ADV748X_IO_DATAPATH 0x03 /* datapath cntrl */
#define ADV748X_IO_DATAPATH_VFREQ_M 0x70
#define ADV748X_IO_DATAPATH_VFREQ_SHIFT 4
#define ADV748X_IO_VID_STD 0x05
#define ADV748X_IO_10 0x10 /* io_reg_10 */
#define ADV748X_IO_10_CSI4_EN BIT(7)
#define ADV748X_IO_10_CSI1_EN BIT(6)
#define ADV748X_IO_10_PIX_OUT_EN BIT(5)
#define ADV748X_IO_10_CSI4_IN_SEL_AFE BIT(3)
#define ADV748X_IO_CHIP_REV_ID_1 0xdf
#define ADV748X_IO_CHIP_REV_ID_2 0xe0
#define ADV748X_IO_REG_F2 0xf2
#define ADV748X_IO_REG_F2_READ_AUTO_INC BIT(0)
/* For PAGE slave address offsets */
#define ADV748X_IO_SLAVE_ADDR_BASE 0xf2
/*
* The ADV748x_Recommended_Settings_PrA_2014-08-20.pdf details both 0x80 and
* 0xff as examples for performing a software reset.
*/
#define ADV748X_IO_REG_FF 0xff
#define ADV748X_IO_REG_FF_MAIN_RESET 0xff
/* HDMI RX Map */
#define ADV748X_HDMI_LW1 0x07 /* line width_1 */
#define ADV748X_HDMI_LW1_VERT_FILTER BIT(7)
#define ADV748X_HDMI_LW1_DE_REGEN BIT(5)
#define ADV748X_HDMI_LW1_WIDTH_MASK 0x1fff
#define ADV748X_HDMI_F0H1 0x09 /* field0 height_1 */
#define ADV748X_HDMI_F0H1_HEIGHT_MASK 0x1fff
#define ADV748X_HDMI_F1H1 0x0b /* field1 height_1 */
#define ADV748X_HDMI_F1H1_INTERLACED BIT(5)
#define ADV748X_HDMI_HFRONT_PORCH 0x20 /* hsync_front_porch_1 */
#define ADV748X_HDMI_HFRONT_PORCH_MASK 0x1fff
#define ADV748X_HDMI_HSYNC_WIDTH 0x22 /* hsync_pulse_width_1 */
#define ADV748X_HDMI_HSYNC_WIDTH_MASK 0x1fff
#define ADV748X_HDMI_HBACK_PORCH 0x24 /* hsync_back_porch_1 */
#define ADV748X_HDMI_HBACK_PORCH_MASK 0x1fff
#define ADV748X_HDMI_VFRONT_PORCH 0x2a /* field0_vs_front_porch_1 */
#define ADV748X_HDMI_VFRONT_PORCH_MASK 0x3fff
#define ADV748X_HDMI_VSYNC_WIDTH 0x2e /* field0_vs_pulse_width_1 */
#define ADV748X_HDMI_VSYNC_WIDTH_MASK 0x3fff
#define ADV748X_HDMI_VBACK_PORCH 0x32 /* field0_vs_back_porch_1 */
#define ADV748X_HDMI_VBACK_PORCH_MASK 0x3fff
#define ADV748X_HDMI_TMDS_1 0x51 /* hdmi_reg_51 */
#define ADV748X_HDMI_TMDS_2 0x52 /* hdmi_reg_52 */
/* HDMI RX Repeater Map */
#define ADV748X_REPEATER_EDID_SZ 0x70 /* primary_edid_size */
#define ADV748X_REPEATER_EDID_SZ_SHIFT 4
#define ADV748X_REPEATER_EDID_CTL 0x74 /* hdcp edid controls */
#define ADV748X_REPEATER_EDID_CTL_EN BIT(0) /* man_edid_a_enable */
/* SDP Main Map */
#define ADV748X_SDP_INSEL 0x00 /* user_map_rw_reg_00 */
#define ADV748X_SDP_VID_SEL 0x02 /* user_map_rw_reg_02 */
#define ADV748X_SDP_VID_SEL_MASK 0xf0
#define ADV748X_SDP_VID_SEL_SHIFT 4
/* Contrast - Unsigned*/
#define ADV748X_SDP_CON 0x08 /* user_map_rw_reg_08 */
#define ADV748X_SDP_CON_MIN 0
#define ADV748X_SDP_CON_DEF 128
#define ADV748X_SDP_CON_MAX 255
/* Brightness - Signed */
#define ADV748X_SDP_BRI 0x0a /* user_map_rw_reg_0a */
#define ADV748X_SDP_BRI_MIN -128
#define ADV748X_SDP_BRI_DEF 0
#define ADV748X_SDP_BRI_MAX 127
/* Hue - Signed, inverted*/
#define ADV748X_SDP_HUE 0x0b /* user_map_rw_reg_0b */
#define ADV748X_SDP_HUE_MIN -127
#define ADV748X_SDP_HUE_DEF 0
#define ADV748X_SDP_HUE_MAX 128
/* Test Patterns / Default Values */
#define ADV748X_SDP_DEF 0x0c /* user_map_rw_reg_0c */
#define ADV748X_SDP_DEF_VAL_EN BIT(0) /* Force free run mode */
#define ADV748X_SDP_DEF_VAL_AUTO_EN BIT(1) /* Free run when no signal */
#define ADV748X_SDP_MAP_SEL 0x0e /* user_map_rw_reg_0e */
#define ADV748X_SDP_MAP_SEL_RO_MAIN 1
/* Free run pattern select */
#define ADV748X_SDP_FRP 0x14
#define ADV748X_SDP_FRP_MASK GENMASK(2, 0)
/* Saturation */
#define ADV748X_SDP_SD_SAT_U 0xe3 /* user_map_rw_reg_e3 */
#define ADV748X_SDP_SD_SAT_V 0xe4 /* user_map_rw_reg_e4 */
#define ADV748X_SDP_SAT_MIN 0
#define ADV748X_SDP_SAT_DEF 128
#define ADV748X_SDP_SAT_MAX 255
/* SDP RO Main Map */
#define ADV748X_SDP_RO_10 0x10
#define ADV748X_SDP_RO_10_IN_LOCK BIT(0)
/* CP Map */
#define ADV748X_CP_PAT_GEN 0x37 /* int_pat_gen_1 */
#define ADV748X_CP_PAT_GEN_EN BIT(7)
/* Contrast Control - Unsigned */
#define ADV748X_CP_CON 0x3a /* contrast_cntrl */
#define ADV748X_CP_CON_MIN 0 /* Minimum contrast */
#define ADV748X_CP_CON_DEF 128 /* Default */
#define ADV748X_CP_CON_MAX 255 /* Maximum contrast */
/* Saturation Control - Unsigned */
#define ADV748X_CP_SAT 0x3b /* saturation_cntrl */
#define ADV748X_CP_SAT_MIN 0 /* Minimum saturation */
#define ADV748X_CP_SAT_DEF 128 /* Default */
#define ADV748X_CP_SAT_MAX 255 /* Maximum saturation */
/* Brightness Control - Signed */
#define ADV748X_CP_BRI 0x3c /* brightness_cntrl */
#define ADV748X_CP_BRI_MIN -128 /* Luma is -512d */
#define ADV748X_CP_BRI_DEF 0 /* Luma is 0 */
#define ADV748X_CP_BRI_MAX 127 /* Luma is 508d */
/* Hue Control */
#define ADV748X_CP_HUE 0x3d /* hue_cntrl */
#define ADV748X_CP_HUE_MIN 0 /* -90 degree */
#define ADV748X_CP_HUE_DEF 0 /* -90 degree */
#define ADV748X_CP_HUE_MAX 255 /* +90 degree */
#define ADV748X_CP_VID_ADJ 0x3e /* vid_adj_0 */
#define ADV748X_CP_VID_ADJ_ENABLE BIT(7) /* Enable colour controls */
#define ADV748X_CP_DE_POS_HIGH 0x8b /* de_pos_adj_6 */
#define ADV748X_CP_DE_POS_HIGH_SET BIT(6)
#define ADV748X_CP_DE_POS_END_LOW 0x8c /* de_pos_adj_7 */
#define ADV748X_CP_DE_POS_START_LOW 0x8d /* de_pos_adj_8 */
#define ADV748X_CP_VID_ADJ_2 0x91
#define ADV748X_CP_VID_ADJ_2_INTERLACED BIT(6)
#define ADV748X_CP_VID_ADJ_2_INTERLACED_3D BIT(4)
#define ADV748X_CP_CLMP_POS 0xc9 /* clmp_pos_cntrl_4 */
#define ADV748X_CP_CLMP_POS_DIS_AUTO BIT(0) /* dis_auto_param_buff */
/* CSI : TXA/TXB Maps */
#define ADV748X_CSI_VC_REF 0x0d /* csi_tx_top_reg_0d */
#define ADV748X_CSI_VC_REF_SHIFT 6
#define ADV748X_CSI_FS_AS_LS 0x1e /* csi_tx_top_reg_1e */
#define ADV748X_CSI_FS_AS_LS_UNKNOWN BIT(6) /* Undocumented bit */
/* Register handling */
int adv748x_read(struct adv748x_state *state, u8 addr, u8 reg);
int adv748x_write(struct adv748x_state *state, u8 page, u8 reg, u8 value);
int adv748x_write_block(struct adv748x_state *state, int client_page,
unsigned int init_reg, const void *val,
size_t val_len);
#define io_read(s, r) adv748x_read(s, ADV748X_PAGE_IO, r)
#define io_write(s, r, v) adv748x_write(s, ADV748X_PAGE_IO, r, v)
#define io_clrset(s, r, m, v) io_write(s, r, (io_read(s, r) & ~(m)) | (v))
#define hdmi_read(s, r) adv748x_read(s, ADV748X_PAGE_HDMI, r)
#define hdmi_read16(s, r, m) (((hdmi_read(s, r) << 8) | hdmi_read(s, (r)+1)) & (m))
#define hdmi_write(s, r, v) adv748x_write(s, ADV748X_PAGE_HDMI, r, v)
#define repeater_read(s, r) adv748x_read(s, ADV748X_PAGE_REPEATER, r)
#define repeater_write(s, r, v) adv748x_write(s, ADV748X_PAGE_REPEATER, r, v)
#define sdp_read(s, r) adv748x_read(s, ADV748X_PAGE_SDP, r)
#define sdp_write(s, r, v) adv748x_write(s, ADV748X_PAGE_SDP, r, v)
#define sdp_clrset(s, r, m, v) sdp_write(s, r, (sdp_read(s, r) & ~(m)) | (v))
#define cp_read(s, r) adv748x_read(s, ADV748X_PAGE_CP, r)
#define cp_write(s, r, v) adv748x_write(s, ADV748X_PAGE_CP, r, v)
#define cp_clrset(s, r, m, v) cp_write(s, r, (cp_read(s, r) & ~(m)) | (v))
#define tx_read(t, r) adv748x_read(t->state, t->page, r)
#define tx_write(t, r, v) adv748x_write(t->state, t->page, r, v)
static inline struct v4l2_subdev *adv748x_get_remote_sd(struct media_pad *pad)
{
pad = media_pad_remote_pad_first(pad);
if (!pad)
return NULL;
return media_entity_to_v4l2_subdev(pad->entity);
}
void adv748x_subdev_init(struct v4l2_subdev *sd, struct adv748x_state *state,
const struct v4l2_subdev_ops *ops, u32 function,
const char *ident);
int adv748x_tx_power(struct adv748x_csi2 *tx, bool on);
int adv748x_afe_init(struct adv748x_afe *afe);
void adv748x_afe_cleanup(struct adv748x_afe *afe);
int adv748x_afe_s_input(struct adv748x_afe *afe, unsigned int input);
int adv748x_csi2_init(struct adv748x_state *state, struct adv748x_csi2 *tx);
void adv748x_csi2_cleanup(struct adv748x_csi2 *tx);
int adv748x_csi2_set_virtual_channel(struct adv748x_csi2 *tx, unsigned int vc);
int adv748x_csi2_set_pixelrate(struct v4l2_subdev *sd, s64 rate);
int adv748x_hdmi_init(struct adv748x_hdmi *hdmi);
void adv748x_hdmi_cleanup(struct adv748x_hdmi *hdmi);
#endif /* _ADV748X_H_ */