aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/staging/media/tegra-video/csi.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/staging/media/tegra-video/csi.c')
-rw-r--r--drivers/staging/media/tegra-video/csi.c314
1 files changed, 284 insertions, 30 deletions
diff --git a/drivers/staging/media/tegra-video/csi.c b/drivers/staging/media/tegra-video/csi.c
index 40ea195d141d..a19c85c57fca 100644
--- a/drivers/staging/media/tegra-video/csi.c
+++ b/drivers/staging/media/tegra-video/csi.c
@@ -9,13 +9,18 @@
#include <linux/host1x.h>
#include <linux/module.h>
#include <linux/of.h>
+#include <linux/of_graph.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
+#include <media/v4l2-fwnode.h>
+
#include "csi.h"
#include "video.h"
+#define MHZ 1000000
+
static inline struct tegra_csi *
host1x_client_to_csi(struct host1x_client *client)
{
@@ -62,6 +67,9 @@ static int csi_enum_bus_code(struct v4l2_subdev *subdev,
struct v4l2_subdev_pad_config *cfg,
struct v4l2_subdev_mbus_code_enum *code)
{
+ if (!IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG))
+ return -ENOIOCTLCMD;
+
if (code->index >= ARRAY_SIZE(tegra_csi_tpg_fmts))
return -EINVAL;
@@ -76,6 +84,9 @@ static int csi_get_format(struct v4l2_subdev *subdev,
{
struct tegra_csi_channel *csi_chan = to_csi_chan(subdev);
+ if (!IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG))
+ return -ENOIOCTLCMD;
+
fmt->format = csi_chan->format;
return 0;
@@ -121,6 +132,9 @@ static int csi_enum_framesizes(struct v4l2_subdev *subdev,
{
unsigned int i;
+ if (!IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG))
+ return -ENOIOCTLCMD;
+
if (fse->index >= ARRAY_SIZE(tegra_csi_tpg_sizes))
return -EINVAL;
@@ -148,6 +162,9 @@ static int csi_enum_frameintervals(struct v4l2_subdev *subdev,
const struct tpg_framerate *frmrate = csi->soc->tpg_frmrate_table;
int index;
+ if (!IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG))
+ return -ENOIOCTLCMD;
+
/* one framerate per format and resolution */
if (fie->index > 0)
return -EINVAL;
@@ -172,6 +189,9 @@ static int csi_set_format(struct v4l2_subdev *subdev,
const struct v4l2_frmsize_discrete *sizes;
unsigned int i;
+ if (!IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG))
+ return -ENOIOCTLCMD;
+
sizes = v4l2_find_nearest_size(tegra_csi_tpg_sizes,
ARRAY_SIZE(tegra_csi_tpg_sizes),
width, height,
@@ -208,40 +228,157 @@ static int tegra_csi_g_frame_interval(struct v4l2_subdev *subdev,
{
struct tegra_csi_channel *csi_chan = to_csi_chan(subdev);
+ if (!IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG))
+ return -ENOIOCTLCMD;
+
vfi->interval.numerator = 1;
vfi->interval.denominator = csi_chan->framerate;
return 0;
}
-static int tegra_csi_s_stream(struct v4l2_subdev *subdev, int enable)
+static unsigned int csi_get_pixel_rate(struct tegra_csi_channel *csi_chan)
+{
+ struct tegra_vi_channel *chan;
+ struct v4l2_subdev *src_subdev;
+ struct v4l2_ctrl *ctrl;
+
+ chan = v4l2_get_subdev_hostdata(&csi_chan->subdev);
+ src_subdev = tegra_channel_get_remote_source_subdev(chan);
+ ctrl = v4l2_ctrl_find(src_subdev->ctrl_handler, V4L2_CID_PIXEL_RATE);
+ if (ctrl)
+ return v4l2_ctrl_g_ctrl_int64(ctrl);
+
+ return 0;
+}
+
+void tegra_csi_calc_settle_time(struct tegra_csi_channel *csi_chan,
+ u8 *clk_settle_time,
+ u8 *ths_settle_time)
+{
+ struct tegra_csi *csi = csi_chan->csi;
+ unsigned int cil_clk_mhz;
+ unsigned int pix_clk_mhz;
+ int clk_idx = (csi_chan->csi_port_num >> 1) + 1;
+
+ cil_clk_mhz = clk_get_rate(csi->clks[clk_idx].clk) / MHZ;
+ pix_clk_mhz = csi_get_pixel_rate(csi_chan) / MHZ;
+
+ /*
+ * CLK Settle time is the interval during which HS receiver should
+ * ignore any clock lane HS transitions, starting from the beginning
+ * of T-CLK-PREPARE.
+ * Per DPHY specification, T-CLK-SETTLE should be between 95ns ~ 300ns
+ *
+ * 95ns < (clk-settle-programmed + 7) * lp clk period < 300ns
+ * midpoint = 197.5 ns
+ */
+ *clk_settle_time = ((95 + 300) * cil_clk_mhz - 14000) / 2000;
+
+ /*
+ * THS Settle time is the interval during which HS receiver should
+ * ignore any data lane HS transitions, starting from the beginning
+ * of THS-PREPARE.
+ *
+ * Per DPHY specification, T-HS-SETTLE should be between 85ns + 6UI
+ * and 145ns+10UI.
+ * 85ns + 6UI < (Ths-settle-prog + 5) * lp_clk_period < 145ns + 10UI
+ * midpoint = 115ns + 8UI
+ */
+ if (pix_clk_mhz)
+ *ths_settle_time = (115 * cil_clk_mhz + 8000 * cil_clk_mhz
+ / (2 * pix_clk_mhz) - 5000) / 1000;
+}
+
+static int tegra_csi_enable_stream(struct v4l2_subdev *subdev)
{
struct tegra_vi_channel *chan = v4l2_get_subdev_hostdata(subdev);
struct tegra_csi_channel *csi_chan = to_csi_chan(subdev);
struct tegra_csi *csi = csi_chan->csi;
- int ret = 0;
+ int ret, err;
- csi_chan->pg_mode = chan->pg_mode;
- if (enable) {
- ret = pm_runtime_get_sync(csi->dev);
+ ret = pm_runtime_get_sync(csi->dev);
+ if (ret < 0) {
+ dev_err(csi->dev, "failed to get runtime PM: %d\n", ret);
+ pm_runtime_put_noidle(csi->dev);
+ return ret;
+ }
+
+ if (csi_chan->mipi) {
+ ret = tegra_mipi_enable(csi_chan->mipi);
if (ret < 0) {
dev_err(csi->dev,
- "failed to get runtime PM: %d\n", ret);
- pm_runtime_put_noidle(csi->dev);
- return ret;
+ "failed to enable MIPI pads: %d\n", ret);
+ goto rpm_put;
}
- ret = csi->ops->csi_start_streaming(csi_chan);
- if (ret < 0)
- goto rpm_put;
+ /*
+ * CSI MIPI pads PULLUP, PULLDN and TERM impedances need to
+ * be calibrated after power on.
+ * So, trigger the calibration start here and results will
+ * be latched and applied to the pads when link is in LP11
+ * state during start of sensor streaming.
+ */
+ ret = tegra_mipi_start_calibration(csi_chan->mipi);
+ if (ret < 0) {
+ dev_err(csi->dev,
+ "failed to start MIPI calibration: %d\n", ret);
+ goto disable_mipi;
+ }
+ }
- return 0;
+ csi_chan->pg_mode = chan->pg_mode;
+ ret = csi->ops->csi_start_streaming(csi_chan);
+ if (ret < 0)
+ goto finish_calibration;
+
+ return 0;
+
+finish_calibration:
+ if (csi_chan->mipi)
+ tegra_mipi_finish_calibration(csi_chan->mipi);
+disable_mipi:
+ if (csi_chan->mipi) {
+ err = tegra_mipi_disable(csi_chan->mipi);
+ if (err < 0)
+ dev_err(csi->dev,
+ "failed to disable MIPI pads: %d\n", err);
}
+rpm_put:
+ pm_runtime_put(csi->dev);
+ return ret;
+}
+
+static int tegra_csi_disable_stream(struct v4l2_subdev *subdev)
+{
+ struct tegra_csi_channel *csi_chan = to_csi_chan(subdev);
+ struct tegra_csi *csi = csi_chan->csi;
+ int err;
+
csi->ops->csi_stop_streaming(csi_chan);
-rpm_put:
+ if (csi_chan->mipi) {
+ err = tegra_mipi_disable(csi_chan->mipi);
+ if (err < 0)
+ dev_err(csi->dev,
+ "failed to disable MIPI pads: %d\n", err);
+ }
+
pm_runtime_put(csi->dev);
+
+ return 0;
+}
+
+static int tegra_csi_s_stream(struct v4l2_subdev *subdev, int enable)
+{
+ int ret;
+
+ if (enable)
+ ret = tegra_csi_enable_stream(subdev);
+ else
+ ret = tegra_csi_disable_stream(subdev);
+
return ret;
}
@@ -267,29 +404,123 @@ static const struct v4l2_subdev_ops tegra_csi_ops = {
.pad = &tegra_csi_pad_ops,
};
+static int tegra_csi_channel_alloc(struct tegra_csi *csi,
+ struct device_node *node,
+ unsigned int port_num, unsigned int lanes,
+ unsigned int num_pads)
+{
+ struct tegra_csi_channel *chan;
+ int ret = 0;
+
+ chan = kzalloc(sizeof(*chan), GFP_KERNEL);
+ if (!chan)
+ return -ENOMEM;
+
+ list_add_tail(&chan->list, &csi->csi_chans);
+ chan->csi = csi;
+ chan->csi_port_num = port_num;
+ chan->numlanes = lanes;
+ chan->of_node = node;
+ chan->numpads = num_pads;
+ if (num_pads & 0x2) {
+ chan->pads[0].flags = MEDIA_PAD_FL_SINK;
+ chan->pads[1].flags = MEDIA_PAD_FL_SOURCE;
+ } else {
+ chan->pads[0].flags = MEDIA_PAD_FL_SOURCE;
+ }
+
+ if (IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG))
+ return 0;
+
+ chan->mipi = tegra_mipi_request(csi->dev, node);
+ if (IS_ERR(chan->mipi)) {
+ ret = PTR_ERR(chan->mipi);
+ dev_err(csi->dev, "failed to get mipi device: %d\n", ret);
+ }
+
+ return ret;
+}
+
static int tegra_csi_tpg_channels_alloc(struct tegra_csi *csi)
{
struct device_node *node = csi->dev->of_node;
unsigned int port_num;
- struct tegra_csi_channel *chan;
unsigned int tpg_channels = csi->soc->csi_max_channels;
+ int ret;
/* allocate CSI channel for each CSI x2 ports */
for (port_num = 0; port_num < tpg_channels; port_num++) {
- chan = kzalloc(sizeof(*chan), GFP_KERNEL);
- if (!chan)
- return -ENOMEM;
-
- list_add_tail(&chan->list, &csi->csi_chans);
- chan->csi = csi;
- chan->csi_port_num = port_num;
- chan->numlanes = 2;
- chan->of_node = node;
- chan->numpads = 1;
- chan->pads[0].flags = MEDIA_PAD_FL_SOURCE;
+ ret = tegra_csi_channel_alloc(csi, node, port_num, 2, 1);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int tegra_csi_channels_alloc(struct tegra_csi *csi)
+{
+ struct device_node *node = csi->dev->of_node;
+ struct v4l2_fwnode_endpoint v4l2_ep = {
+ .bus_type = V4L2_MBUS_CSI2_DPHY
+ };
+ struct fwnode_handle *fwh;
+ struct device_node *channel;
+ struct device_node *ep;
+ unsigned int lanes, portno, num_pads;
+ int ret;
+
+ for_each_child_of_node(node, channel) {
+ if (!of_node_name_eq(channel, "channel"))
+ continue;
+
+ ret = of_property_read_u32(channel, "reg", &portno);
+ if (ret < 0)
+ continue;
+
+ if (portno >= csi->soc->csi_max_channels) {
+ dev_err(csi->dev, "invalid port num %d for %pOF\n",
+ portno, channel);
+ ret = -EINVAL;
+ goto err_node_put;
+ }
+
+ ep = of_graph_get_endpoint_by_regs(channel, 0, 0);
+ if (!ep)
+ continue;
+
+ fwh = of_fwnode_handle(ep);
+ ret = v4l2_fwnode_endpoint_parse(fwh, &v4l2_ep);
+ of_node_put(ep);
+ if (ret) {
+ dev_err(csi->dev,
+ "failed to parse v4l2 endpoint for %pOF: %d\n",
+ channel, ret);
+ goto err_node_put;
+ }
+
+ lanes = v4l2_ep.bus.mipi_csi2.num_data_lanes;
+ if (!lanes || ((lanes & (lanes - 1)) != 0)) {
+ dev_err(csi->dev, "invalid data-lanes %d for %pOF\n",
+ lanes, channel);
+ ret = -EINVAL;
+ goto err_node_put;
+ }
+
+ num_pads = of_graph_get_endpoint_count(channel);
+ if (num_pads == TEGRA_CSI_PADS_NUM) {
+ ret = tegra_csi_channel_alloc(csi, channel, portno,
+ lanes, num_pads);
+ if (ret < 0)
+ goto err_node_put;
+ }
}
return 0;
+
+err_node_put:
+ of_node_put(channel);
+ return ret;
}
static int tegra_csi_channel_init(struct tegra_csi_channel *chan)
@@ -311,8 +542,12 @@ static int tegra_csi_channel_init(struct tegra_csi_channel *chan)
subdev = &chan->subdev;
v4l2_subdev_init(subdev, &tegra_csi_ops);
subdev->dev = csi->dev;
- snprintf(subdev->name, V4L2_SUBDEV_NAME_SIZE, "%s-%d", "tpg",
- chan->csi_port_num);
+ if (IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG))
+ snprintf(subdev->name, V4L2_SUBDEV_NAME_SIZE, "%s-%d", "tpg",
+ chan->csi_port_num);
+ else
+ snprintf(subdev->name, V4L2_SUBDEV_NAME_SIZE, "%s",
+ kbasename(chan->of_node->full_name));
v4l2_set_subdevdata(subdev, chan);
subdev->fwnode = of_fwnode_handle(chan->of_node);
@@ -328,6 +563,15 @@ static int tegra_csi_channel_init(struct tegra_csi_channel *chan)
return ret;
}
+ if (!IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG)) {
+ ret = v4l2_async_register_subdev(subdev);
+ if (ret < 0) {
+ dev_err(csi->dev,
+ "failed to register subdev: %d\n", ret);
+ return ret;
+ }
+ }
+
return 0;
}
@@ -366,9 +610,16 @@ static void tegra_csi_channels_cleanup(struct tegra_csi *csi)
struct tegra_csi_channel *chan, *tmp;
list_for_each_entry_safe(chan, tmp, &csi->csi_chans, list) {
+ if (chan->mipi)
+ tegra_mipi_free(chan->mipi);
+
subdev = &chan->subdev;
- if (subdev->dev)
+ if (subdev->dev) {
+ if (!IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG))
+ v4l2_async_unregister_subdev(subdev);
media_entity_cleanup(&subdev->entity);
+ }
+
list_del(&chan->list);
kfree(chan);
}
@@ -405,10 +656,13 @@ static int tegra_csi_init(struct host1x_client *client)
INIT_LIST_HEAD(&csi->csi_chans);
- ret = tegra_csi_tpg_channels_alloc(csi);
+ if (IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG))
+ ret = tegra_csi_tpg_channels_alloc(csi);
+ else
+ ret = tegra_csi_channels_alloc(csi);
if (ret < 0) {
dev_err(csi->dev,
- "failed to allocate tpg channels: %d\n", ret);
+ "failed to allocate channels: %d\n", ret);
goto cleanup;
}