mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-12-20 09:34:44 +08:00
3e89586a64
Provide support for the ADV7481 and ADV7482. The driver is modelled with 4 subdevices to allow simultaneous streaming from the AFE (Analog front end) and HDMI inputs though two CSI TX entities. The HDMI entity is linked to the TXA CSI bus, whilst the AFE is linked to the TXB CSI bus. The driver is based on a prototype by Koji Matsuoka in the Renesas BSP, and an earlier rework by Niklas Söderlund. Signed-off-by: Kieran Bingham <kieran.bingham+renesas@ideasonboard.com> Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com> Signed-off-by: Mauro Carvalho Chehab <mchehab@s-opensource.com>
833 lines
22 KiB
C
833 lines
22 KiB
C
/*
|
|
* Driver for Analog Devices ADV748X HDMI receiver with AFE
|
|
*
|
|
* Copyright (C) 2017 Renesas Electronics Corp.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation; either version 2 of the License, or (at your
|
|
* option) any later version.
|
|
*
|
|
* 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-ioctl.h>
|
|
|
|
#include "adv748x.h"
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* Register manipulation
|
|
*/
|
|
|
|
static const struct regmap_config adv748x_regmap_cnf[] = {
|
|
{
|
|
.name = "io",
|
|
.reg_bits = 8,
|
|
.val_bits = 8,
|
|
|
|
.max_register = 0xff,
|
|
.cache_type = REGCACHE_NONE,
|
|
},
|
|
{
|
|
.name = "dpll",
|
|
.reg_bits = 8,
|
|
.val_bits = 8,
|
|
|
|
.max_register = 0xff,
|
|
.cache_type = REGCACHE_NONE,
|
|
},
|
|
{
|
|
.name = "cp",
|
|
.reg_bits = 8,
|
|
.val_bits = 8,
|
|
|
|
.max_register = 0xff,
|
|
.cache_type = REGCACHE_NONE,
|
|
},
|
|
{
|
|
.name = "hdmi",
|
|
.reg_bits = 8,
|
|
.val_bits = 8,
|
|
|
|
.max_register = 0xff,
|
|
.cache_type = REGCACHE_NONE,
|
|
},
|
|
{
|
|
.name = "edid",
|
|
.reg_bits = 8,
|
|
.val_bits = 8,
|
|
|
|
.max_register = 0xff,
|
|
.cache_type = REGCACHE_NONE,
|
|
},
|
|
{
|
|
.name = "repeater",
|
|
.reg_bits = 8,
|
|
.val_bits = 8,
|
|
|
|
.max_register = 0xff,
|
|
.cache_type = REGCACHE_NONE,
|
|
},
|
|
{
|
|
.name = "infoframe",
|
|
.reg_bits = 8,
|
|
.val_bits = 8,
|
|
|
|
.max_register = 0xff,
|
|
.cache_type = REGCACHE_NONE,
|
|
},
|
|
{
|
|
.name = "cec",
|
|
.reg_bits = 8,
|
|
.val_bits = 8,
|
|
|
|
.max_register = 0xff,
|
|
.cache_type = REGCACHE_NONE,
|
|
},
|
|
{
|
|
.name = "sdp",
|
|
.reg_bits = 8,
|
|
.val_bits = 8,
|
|
|
|
.max_register = 0xff,
|
|
.cache_type = REGCACHE_NONE,
|
|
},
|
|
|
|
{
|
|
.name = "txb",
|
|
.reg_bits = 8,
|
|
.val_bits = 8,
|
|
|
|
.max_register = 0xff,
|
|
.cache_type = REGCACHE_NONE,
|
|
},
|
|
{
|
|
.name = "txa",
|
|
.reg_bits = 8,
|
|
.val_bits = 8,
|
|
|
|
.max_register = 0xff,
|
|
.cache_type = REGCACHE_NONE,
|
|
},
|
|
};
|
|
|
|
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;
|
|
}
|
|
|
|
/* Default addresses for the I2C pages */
|
|
static int adv748x_i2c_addresses[ADV748X_PAGE_MAX] = {
|
|
ADV748X_I2C_IO,
|
|
ADV748X_I2C_DPLL,
|
|
ADV748X_I2C_CP,
|
|
ADV748X_I2C_HDMI,
|
|
ADV748X_I2C_EDID,
|
|
ADV748X_I2C_REPEATER,
|
|
ADV748X_I2C_INFOFRAME,
|
|
ADV748X_I2C_CEC,
|
|
ADV748X_I2C_SDP,
|
|
ADV748X_I2C_TXB,
|
|
ADV748X_I2C_TXA,
|
|
};
|
|
|
|
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);
|
|
}
|
|
|
|
/* 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 struct i2c_client *adv748x_dummy_client(struct adv748x_state *state,
|
|
u8 addr, u8 io_reg)
|
|
{
|
|
struct i2c_client *client = state->client;
|
|
|
|
if (addr)
|
|
io_write(state, io_reg, addr << 1);
|
|
|
|
return i2c_new_dummy(client->adapter, io_read(state, io_reg) >> 1);
|
|
}
|
|
|
|
static void adv748x_unregister_clients(struct adv748x_state *state)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 1; i < ARRAY_SIZE(state->i2c_clients); ++i) {
|
|
if (state->i2c_clients[i])
|
|
i2c_unregister_device(state->i2c_clients[i]);
|
|
}
|
|
}
|
|
|
|
static int adv748x_initialise_clients(struct adv748x_state *state)
|
|
{
|
|
int i;
|
|
int ret;
|
|
|
|
for (i = ADV748X_PAGE_DPLL; i < ADV748X_PAGE_MAX; ++i) {
|
|
state->i2c_clients[i] =
|
|
adv748x_dummy_client(state, adv748x_i2c_addresses[i],
|
|
ADV748X_IO_SLAVE_ADDR_BASE + i);
|
|
if (state->i2c_clients[i] == NULL) {
|
|
adv_err(state, "failed to create i2c client %u\n", i);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
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;
|
|
|
|
while (regs->page != ADV748X_PAGE_EOR) {
|
|
if (regs->page == ADV748X_PAGE_WAIT) {
|
|
msleep(regs->value);
|
|
} else {
|
|
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;
|
|
}
|
|
}
|
|
regs++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* TXA and TXB
|
|
*/
|
|
|
|
static const struct adv748x_reg_value adv748x_power_up_txa_4lane[] = {
|
|
|
|
{ADV748X_PAGE_TXA, 0x00, 0x84}, /* Enable 4-lane MIPI */
|
|
{ADV748X_PAGE_TXA, 0x00, 0xa4}, /* Set Auto DPHY Timing */
|
|
|
|
{ADV748X_PAGE_TXA, 0x31, 0x82}, /* ADI Required Write */
|
|
{ADV748X_PAGE_TXA, 0x1e, 0x40}, /* ADI Required Write */
|
|
{ADV748X_PAGE_TXA, 0xda, 0x01}, /* i2c_mipi_pll_en - 1'b1 */
|
|
{ADV748X_PAGE_WAIT, 0x00, 0x02},/* delay 2 */
|
|
{ADV748X_PAGE_TXA, 0x00, 0x24 },/* Power-up CSI-TX */
|
|
{ADV748X_PAGE_WAIT, 0x00, 0x01},/* delay 1 */
|
|
{ADV748X_PAGE_TXA, 0xc1, 0x2b}, /* ADI Required Write */
|
|
{ADV748X_PAGE_WAIT, 0x00, 0x01},/* delay 1 */
|
|
{ADV748X_PAGE_TXA, 0x31, 0x80}, /* ADI Required Write */
|
|
|
|
{ADV748X_PAGE_EOR, 0xff, 0xff} /* End of register table */
|
|
};
|
|
|
|
static const struct adv748x_reg_value adv748x_power_down_txa_4lane[] = {
|
|
|
|
{ADV748X_PAGE_TXA, 0x31, 0x82}, /* ADI Required Write */
|
|
{ADV748X_PAGE_TXA, 0x1e, 0x00}, /* ADI Required Write */
|
|
{ADV748X_PAGE_TXA, 0x00, 0x84}, /* Enable 4-lane MIPI */
|
|
{ADV748X_PAGE_TXA, 0xda, 0x01}, /* i2c_mipi_pll_en - 1'b1 */
|
|
{ADV748X_PAGE_TXA, 0xc1, 0x3b}, /* ADI Required Write */
|
|
|
|
{ADV748X_PAGE_EOR, 0xff, 0xff} /* End of register table */
|
|
};
|
|
|
|
static const struct adv748x_reg_value adv748x_power_up_txb_1lane[] = {
|
|
|
|
{ADV748X_PAGE_TXB, 0x00, 0x81}, /* Enable 1-lane MIPI */
|
|
{ADV748X_PAGE_TXB, 0x00, 0xa1}, /* Set Auto DPHY Timing */
|
|
|
|
{ADV748X_PAGE_TXB, 0x31, 0x82}, /* ADI Required Write */
|
|
{ADV748X_PAGE_TXB, 0x1e, 0x40}, /* ADI Required Write */
|
|
{ADV748X_PAGE_TXB, 0xda, 0x01}, /* i2c_mipi_pll_en - 1'b1 */
|
|
{ADV748X_PAGE_WAIT, 0x00, 0x02},/* delay 2 */
|
|
{ADV748X_PAGE_TXB, 0x00, 0x21 },/* Power-up CSI-TX */
|
|
{ADV748X_PAGE_WAIT, 0x00, 0x01},/* delay 1 */
|
|
{ADV748X_PAGE_TXB, 0xc1, 0x2b}, /* ADI Required Write */
|
|
{ADV748X_PAGE_WAIT, 0x00, 0x01},/* delay 1 */
|
|
{ADV748X_PAGE_TXB, 0x31, 0x80}, /* ADI Required Write */
|
|
|
|
{ADV748X_PAGE_EOR, 0xff, 0xff} /* End of register table */
|
|
};
|
|
|
|
static const struct adv748x_reg_value adv748x_power_down_txb_1lane[] = {
|
|
|
|
{ADV748X_PAGE_TXB, 0x31, 0x82}, /* ADI Required Write */
|
|
{ADV748X_PAGE_TXB, 0x1e, 0x00}, /* ADI Required Write */
|
|
{ADV748X_PAGE_TXB, 0x00, 0x81}, /* Enable 4-lane MIPI */
|
|
{ADV748X_PAGE_TXB, 0xda, 0x01}, /* i2c_mipi_pll_en - 1'b1 */
|
|
{ADV748X_PAGE_TXB, 0xc1, 0x3b}, /* ADI Required Write */
|
|
|
|
{ADV748X_PAGE_EOR, 0xff, 0xff} /* End of register table */
|
|
};
|
|
|
|
int adv748x_txa_power(struct adv748x_state *state, bool on)
|
|
{
|
|
int val;
|
|
|
|
val = txa_read(state, 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");
|
|
|
|
if (on)
|
|
return adv748x_write_regs(state, adv748x_power_up_txa_4lane);
|
|
|
|
return adv748x_write_regs(state, adv748x_power_down_txa_4lane);
|
|
}
|
|
|
|
int adv748x_txb_power(struct adv748x_state *state, bool on)
|
|
{
|
|
int val;
|
|
|
|
val = txb_read(state, 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");
|
|
|
|
if (on)
|
|
return adv748x_write_regs(state, adv748x_power_up_txb_1lane);
|
|
|
|
return adv748x_write_regs(state, adv748x_power_down_txb_1lane);
|
|
}
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* Media Operations
|
|
*/
|
|
|
|
static const struct media_entity_operations adv748x_media_ops = {
|
|
.link_validate = v4l2_subdev_link_validate,
|
|
};
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* HW setup
|
|
*/
|
|
|
|
static const struct adv748x_reg_value adv748x_sw_reset[] = {
|
|
|
|
{ADV748X_PAGE_IO, 0xff, 0xff}, /* SW reset */
|
|
{ADV748X_PAGE_WAIT, 0x00, 0x05},/* delay 5 */
|
|
{ADV748X_PAGE_IO, 0x01, 0x76}, /* ADI Required Write */
|
|
{ADV748X_PAGE_IO, 0xf2, 0x01}, /* Enable I2C Read Auto-Increment */
|
|
{ADV748X_PAGE_EOR, 0xff, 0xff} /* End of register table */
|
|
};
|
|
|
|
static const struct adv748x_reg_value adv748x_set_slave_address[] = {
|
|
{ADV748X_PAGE_IO, 0xf3, ADV748X_I2C_DPLL << 1},
|
|
{ADV748X_PAGE_IO, 0xf4, ADV748X_I2C_CP << 1},
|
|
{ADV748X_PAGE_IO, 0xf5, ADV748X_I2C_HDMI << 1},
|
|
{ADV748X_PAGE_IO, 0xf6, ADV748X_I2C_EDID << 1},
|
|
{ADV748X_PAGE_IO, 0xf7, ADV748X_I2C_REPEATER << 1},
|
|
{ADV748X_PAGE_IO, 0xf8, ADV748X_I2C_INFOFRAME << 1},
|
|
{ADV748X_PAGE_IO, 0xfa, ADV748X_I2C_CEC << 1},
|
|
{ADV748X_PAGE_IO, 0xfb, ADV748X_I2C_SDP << 1},
|
|
{ADV748X_PAGE_IO, 0xfc, ADV748X_I2C_TXB << 1},
|
|
{ADV748X_PAGE_IO, 0xfd, ADV748X_I2C_TXA << 1},
|
|
{ADV748X_PAGE_EOR, 0xff, 0xff} /* End of register table */
|
|
};
|
|
|
|
/* Supported Formats For Script Below */
|
|
/* - 01-29 HDMI to MIPI TxA CSI 4-Lane - RGB888: */
|
|
static const struct adv748x_reg_value adv748x_init_txa_4lane[] = {
|
|
/* 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 */
|
|
/* Outputs Enabled */
|
|
{ADV748X_PAGE_IO, 0x10, 0xa0}, /* Enable 4-lane CSI Tx & Pixel Port */
|
|
|
|
{ADV748X_PAGE_TXA, 0x00, 0x84}, /* Enable 4-lane MIPI */
|
|
{ADV748X_PAGE_TXA, 0x00, 0xa4}, /* Set Auto DPHY Timing */
|
|
{ADV748X_PAGE_TXA, 0xdb, 0x10}, /* ADI Required Write */
|
|
{ADV748X_PAGE_TXA, 0xd6, 0x07}, /* ADI Required Write */
|
|
{ADV748X_PAGE_TXA, 0xc4, 0x0a}, /* ADI Required Write */
|
|
{ADV748X_PAGE_TXA, 0x71, 0x33}, /* ADI Required Write */
|
|
{ADV748X_PAGE_TXA, 0x72, 0x11}, /* ADI Required Write */
|
|
{ADV748X_PAGE_TXA, 0xf0, 0x00}, /* i2c_dphy_pwdn - 1'b0 */
|
|
|
|
{ADV748X_PAGE_TXA, 0x31, 0x82}, /* ADI Required Write */
|
|
{ADV748X_PAGE_TXA, 0x1e, 0x40}, /* ADI Required Write */
|
|
{ADV748X_PAGE_TXA, 0xda, 0x01}, /* i2c_mipi_pll_en - 1'b1 */
|
|
{ADV748X_PAGE_WAIT, 0x00, 0x02},/* delay 2 */
|
|
{ADV748X_PAGE_TXA, 0x00, 0x24 },/* Power-up CSI-TX */
|
|
{ADV748X_PAGE_WAIT, 0x00, 0x01},/* delay 1 */
|
|
{ADV748X_PAGE_TXA, 0xc1, 0x2b}, /* ADI Required Write */
|
|
{ADV748X_PAGE_WAIT, 0x00, 0x01},/* delay 1 */
|
|
{ADV748X_PAGE_TXA, 0x31, 0x80}, /* ADI Required Write */
|
|
|
|
{ADV748X_PAGE_EOR, 0xff, 0xff} /* End of register table */
|
|
};
|
|
|
|
/* 02-01 Analog CVBS to MIPI TX-B CSI 1-Lane - */
|
|
/* Autodetect CVBS Single Ended In Ain 1 - MIPI Out */
|
|
static const struct adv748x_reg_value adv748x_init_txb_1lane[] = {
|
|
|
|
{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 */
|
|
|
|
/* Enable 1-Lane MIPI Tx, */
|
|
/* enable pixel output and route SD through Pixel port */
|
|
{ADV748X_PAGE_IO, 0x10, 0x70},
|
|
|
|
{ADV748X_PAGE_TXB, 0x00, 0x81}, /* Enable 1-lane MIPI */
|
|
{ADV748X_PAGE_TXB, 0x00, 0xa1}, /* Set Auto DPHY Timing */
|
|
{ADV748X_PAGE_TXB, 0xd2, 0x40}, /* ADI Required Write */
|
|
{ADV748X_PAGE_TXB, 0xc4, 0x0a}, /* ADI Required Write */
|
|
{ADV748X_PAGE_TXB, 0x71, 0x33}, /* ADI Required Write */
|
|
{ADV748X_PAGE_TXB, 0x72, 0x11}, /* ADI Required Write */
|
|
{ADV748X_PAGE_TXB, 0xf0, 0x00}, /* i2c_dphy_pwdn - 1'b0 */
|
|
{ADV748X_PAGE_TXB, 0x31, 0x82}, /* ADI Required Write */
|
|
{ADV748X_PAGE_TXB, 0x1e, 0x40}, /* ADI Required Write */
|
|
{ADV748X_PAGE_TXB, 0xda, 0x01}, /* i2c_mipi_pll_en - 1'b1 */
|
|
|
|
{ADV748X_PAGE_WAIT, 0x00, 0x02},/* delay 2 */
|
|
{ADV748X_PAGE_TXB, 0x00, 0x21 },/* Power-up CSI-TX */
|
|
{ADV748X_PAGE_WAIT, 0x00, 0x01},/* delay 1 */
|
|
{ADV748X_PAGE_TXB, 0xc1, 0x2b}, /* ADI Required Write */
|
|
{ADV748X_PAGE_WAIT, 0x00, 0x01},/* delay 1 */
|
|
{ADV748X_PAGE_TXB, 0x31, 0x80}, /* ADI Required Write */
|
|
|
|
{ADV748X_PAGE_EOR, 0xff, 0xff} /* End of register table */
|
|
};
|
|
|
|
static int adv748x_reset(struct adv748x_state *state)
|
|
{
|
|
int ret;
|
|
|
|
ret = adv748x_write_regs(state, adv748x_sw_reset);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = adv748x_write_regs(state, adv748x_set_slave_address);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/* Init and power down TXA */
|
|
ret = adv748x_write_regs(state, adv748x_init_txa_4lane);
|
|
if (ret)
|
|
return ret;
|
|
|
|
adv748x_txa_power(state, 0);
|
|
|
|
/* Init and power down TXB */
|
|
ret = adv748x_write_regs(state, adv748x_init_txb_1lane);
|
|
if (ret)
|
|
return ret;
|
|
|
|
adv748x_txb_power(state, 0);
|
|
|
|
/* Disable chip powerdown & Enable HDMI Rx block */
|
|
io_write(state, ADV748X_IO_PD, ADV748X_IO_PD_RX_EN);
|
|
|
|
/* Enable 4-lane CSI Tx & Pixel Port */
|
|
io_write(state, ADV748X_IO_10, ADV748X_IO_10_CSI4_EN |
|
|
ADV748X_IO_10_CSI1_EN |
|
|
ADV748X_IO_10_PIX_OUT_EN);
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* 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 = &adv748x_media_ops;
|
|
}
|
|
|
|
static int adv748x_parse_dt(struct adv748x_state *state)
|
|
{
|
|
struct device_node *ep_np = NULL;
|
|
struct of_endpoint ep;
|
|
bool found = false;
|
|
|
|
for_each_endpoint_of_node(state->dev->of_node, ep_np) {
|
|
of_graph_parse_endpoint(ep_np, &ep);
|
|
adv_info(state, "Endpoint %s on port %d",
|
|
of_node_full_name(ep.local_node),
|
|
ep.port);
|
|
|
|
if (ep.port >= ADV748X_PORT_MAX) {
|
|
adv_err(state, "Invalid endpoint %s on port %d",
|
|
of_node_full_name(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;
|
|
|
|
found = true;
|
|
}
|
|
|
|
return 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,
|
|
const struct i2c_device_id *id)
|
|
{
|
|
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 = kzalloc(sizeof(struct adv748x_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);
|
|
|
|
/* 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_clients;
|
|
}
|
|
|
|
/* 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);
|
|
kfree(state);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int 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);
|
|
|
|
kfree(state);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct i2c_device_id adv748x_id[] = {
|
|
{ "adv7481", 0 },
|
|
{ "adv7482", 0 },
|
|
{ },
|
|
};
|
|
|
|
static const struct of_device_id adv748x_of_table[] = {
|
|
{ .compatible = "adi,adv7481", },
|
|
{ .compatible = "adi,adv7482", },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(of, adv748x_of_table);
|
|
|
|
static struct i2c_driver adv748x_driver = {
|
|
.driver = {
|
|
.name = "adv748x",
|
|
.of_match_table = adv748x_of_table,
|
|
},
|
|
.probe = adv748x_probe,
|
|
.remove = adv748x_remove,
|
|
.id_table = adv748x_id,
|
|
};
|
|
|
|
module_i2c_driver(adv748x_driver);
|
|
|
|
MODULE_AUTHOR("Kieran Bingham <kieran.bingham@ideasonboard.com>");
|
|
MODULE_DESCRIPTION("ADV748X video decoder");
|
|
MODULE_LICENSE("GPL v2");
|