#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
+#include <linux/of_device.h>
#include <linux/of_graph.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
+#include <linux/reset.h>
#include <linux/slab.h>
#include <media/v4l2-ctrls.h>
#define CSI2RX_STATIC_CFG_DLANE_MAP(llane, plane) ((plane) << (16 + (llane) * 4))
#define CSI2RX_STATIC_CFG_LANES_MASK GENMASK(11, 8)
+#define CSI2RX_DPHY_LANE_CTRL_REG 0x40
+#define CSI2RX_DPHY_CL_RST BIT(16)
+#define CSI2RX_DPHY_DL_RST(i) BIT((i) + 12)
+#define CSI2RX_DPHY_CL_EN BIT(4)
+#define CSI2RX_DPHY_DL_EN(i) BIT(i)
+
#define CSI2RX_STREAM_BASE(n) (((n) + 1) * 0x100)
#define CSI2RX_STREAM_CTRL_REG(n) (CSI2RX_STREAM_BASE(n) + 0x000)
#define CSI2RX_STREAM_DATA_CFG_REG(n) (CSI2RX_STREAM_BASE(n) + 0x008)
#define CSI2RX_STREAM_DATA_CFG_EN_VC_SELECT BIT(31)
+#define CSI2RX_STREAM_DATA_CFG_EN_DATA_TYPE_0 BIT(7)
#define CSI2RX_STREAM_DATA_CFG_VC_SELECT(n) BIT((n) + 16)
#define CSI2RX_STREAM_CFG_REG(n) (CSI2RX_STREAM_BASE(n) + 0x00c)
CSI2RX_PAD_MAX,
};
+struct csi2rx_fmt {
+ u32 code;
+ u8 bpp;
+ u32 dt;
+};
+
+struct csi2rx_platform_info {
+ unsigned long sys_clk_rate;
+};
+
struct csi2rx_priv {
struct device *dev;
+ unsigned int power_count;
unsigned int count;
/*
struct clk *sys_clk;
struct clk *p_clk;
struct clk *pixel_clk[CSI2RX_STREAMS_MAX];
+ struct reset_control *sys_rst;
+ struct reset_control *p_rst;
+ struct reset_control *pixel_rst[CSI2RX_STREAMS_MAX];
struct phy *dphy;
u8 lanes[CSI2RX_LANES_MAX];
/* Remote source */
struct v4l2_subdev *source_subdev;
int source_pad;
+
+ const struct csi2rx_platform_info *platform_info;
+};
+
+static const struct csi2rx_fmt formats[] = {
+ {
+ .code = MEDIA_BUS_FMT_SRGGB10_1X10,
+ .bpp = 10,
+ .dt = 0x2b,
+ },
+ {
+ .code = MEDIA_BUS_FMT_SGRBG10_1X10,
+ .bpp = 10,
+ .dt = 0x2b,
+ },
+ {
+ .code = MEDIA_BUS_FMT_SGBRG10_1X10,
+ .bpp = 10,
+ .dt = 0x2b,
+ },
+ {
+ .code = MEDIA_BUS_FMT_SBGGR10_1X10,
+ .bpp = 10,
+ .dt = 0x2b,
+ },
};
+static u8 csi2rx_get_bpp(u32 code)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(formats); i++) {
+ if (formats[i].code == code)
+ return formats[i].bpp;
+ }
+
+ return 0;
+}
+
+static u32 csi2rx_get_dt(u32 code)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(formats); i++) {
+ if (formats[i].code == code)
+ return formats[i].dt;
+ }
+
+ return 0;
+}
+
+static s64 csi2rx_get_pixel_rate(struct csi2rx_priv *csi2rx)
+{
+ struct v4l2_ctrl *ctrl;
+
+ ctrl = v4l2_ctrl_find(csi2rx->source_subdev->ctrl_handler,
+ V4L2_CID_PIXEL_RATE);
+ if (!ctrl) {
+ dev_err(csi2rx->dev, "no pixel rate control in subdev: %s\n",
+ csi2rx->source_subdev->name);
+ return -EINVAL;
+ }
+
+ return v4l2_ctrl_g_ctrl_int64(ctrl);
+}
+
static inline
struct csi2rx_priv *v4l2_subdev_to_csi2rx(struct v4l2_subdev *subdev)
{
return container_of(subdev, struct csi2rx_priv, subdev);
}
+static int csi2rx_s_power(struct v4l2_subdev *subdev, int on)
+{
+ struct csi2rx_priv *csi2rx = v4l2_subdev_to_csi2rx(subdev);
+
+ mutex_lock(&csi2rx->lock);
+
+ if (on) {
+ if (!csi2rx->power_count)
+ phy_init(csi2rx->dphy);
+
+ csi2rx->power_count++;
+ } else {
+ csi2rx->power_count--;
+
+ if (!csi2rx->power_count)
+ phy_exit(csi2rx->dphy);
+ }
+
+ mutex_unlock(&csi2rx->lock);
+ return 0;
+}
+
static void csi2rx_reset(struct csi2rx_priv *csi2rx)
{
writel(CSI2RX_SOFT_RESET_PROTOCOL | CSI2RX_SOFT_RESET_FRONT,
writel(0, csi2rx->base + CSI2RX_SOFT_RESET_REG);
}
+static int csi2rx_configure_ext_dphy(struct csi2rx_priv *csi2rx)
+{
+ union phy_configure_opts opts = { };
+ struct phy_configure_opts_mipi_dphy *cfg = &opts.mipi_dphy;
+ struct v4l2_subdev_format sd_fmt;
+ s64 pixel_rate;
+ int ret;
+ u8 bpp;
+
+ sd_fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
+ sd_fmt.pad = 0;
+
+ ret = v4l2_subdev_call(csi2rx->source_subdev, pad, get_fmt, NULL,
+ &sd_fmt);
+ if (ret)
+ return ret;
+
+ bpp = csi2rx_get_bpp(sd_fmt.format.code);
+ if (!bpp)
+ return -EINVAL;
+
+ pixel_rate = csi2rx_get_pixel_rate(csi2rx);
+ if (pixel_rate < 0)
+ return pixel_rate;
+
+ ret = phy_mipi_dphy_get_default_config(pixel_rate, bpp,
+ csi2rx->num_lanes, cfg);
+ if (ret)
+ return ret;
+
+ phy_pm_runtime_get_sync(csi2rx->dphy);
+
+ ret = phy_power_on(csi2rx->dphy);
+ if (ret)
+ goto out;
+
+ ret = phy_configure(csi2rx->dphy, &opts);
+ if (ret) {
+ /* Can't do anything if it fails. Ignore the return value. */
+ phy_power_off(csi2rx->dphy);
+ goto out;
+ }
+
+out:
+ phy_pm_runtime_put_sync(csi2rx->dphy);
+
+ return ret;
+}
+
static int csi2rx_start(struct csi2rx_priv *csi2rx)
{
+ struct v4l2_subdev_format sd_fmt;
unsigned int i;
unsigned long lanes_used = 0;
u32 reg;
+ u32 dt = 0;
int ret;
ret = clk_prepare_enable(csi2rx->p_clk);
if (ret)
return ret;
+ reset_control_deassert(csi2rx->p_rst);
+
csi2rx_reset(csi2rx);
reg = csi2rx->num_lanes << 8;
if (ret)
goto err_disable_pclk;
+ /* Enable DPHY clk and data lanes. */
+ if (csi2rx->dphy) {
+ reg = CSI2RX_DPHY_CL_EN | CSI2RX_DPHY_CL_RST;
+ for (i = 0; i < csi2rx->num_lanes; i++) {
+ reg |= CSI2RX_DPHY_DL_EN(csi2rx->lanes[i] - 1);
+ reg |= CSI2RX_DPHY_DL_RST(csi2rx->lanes[i] - 1);
+ }
+
+ writel(reg, csi2rx->base + CSI2RX_DPHY_LANE_CTRL_REG);
+ }
+
+ sd_fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
+ sd_fmt.pad = 0;
+
+ ret = v4l2_subdev_call(csi2rx->source_subdev, pad, get_fmt, NULL,
+ &sd_fmt);
+ if (ret)
+ dev_warn(csi2rx->dev, "Couldn't get format\n");
+
+ dt = csi2rx_get_dt(sd_fmt.format.code);
+ if (!dt)
+ dev_warn(csi2rx->dev, "Couldn't get dt\n");
+
/*
* Create a static mapping between the CSI virtual channels
* and the output stream.
if (ret)
goto err_disable_pixclk;
+ reset_control_deassert(csi2rx->pixel_rst[i]);
+
writel(CSI2RX_STREAM_CFG_FIFO_MODE_LARGE_BUF,
csi2rx->base + CSI2RX_STREAM_CFG_REG(i));
CSI2RX_STREAM_DATA_CFG_VC_SELECT(i),
csi2rx->base + CSI2RX_STREAM_DATA_CFG_REG(i));
+ if (dt)
+ writel(readl(csi2rx->base + CSI2RX_STREAM_DATA_CFG_REG(i)) |
+ CSI2RX_STREAM_DATA_CFG_EN_DATA_TYPE_0 | dt,
+ csi2rx->base + CSI2RX_STREAM_DATA_CFG_REG(i));
+
writel(CSI2RX_STREAM_CTRL_START,
csi2rx->base + CSI2RX_STREAM_CTRL_REG(i));
}
if (ret)
goto err_disable_pixclk;
+ if (csi2rx->platform_info && csi2rx->platform_info->sys_clk_rate > 0)
+ clk_set_rate(csi2rx->sys_clk,
+ csi2rx->platform_info->sys_clk_rate);
+
+ reset_control_deassert(csi2rx->sys_rst);
+
+ if (csi2rx->dphy) {
+ ret = csi2rx_configure_ext_dphy(csi2rx);
+ if (ret) {
+ dev_err(csi2rx->dev,
+ "Failed to configure external DPHY: %d\n", ret);
+ goto err_disable_sysclk;
+ }
+ }
+
clk_disable_unprepare(csi2rx->p_clk);
return 0;
+err_disable_sysclk:
+ clk_disable_unprepare(csi2rx->sys_clk);
err_disable_pixclk:
for (; i > 0; i--)
clk_disable_unprepare(csi2rx->pixel_clk[i - 1]);
unsigned int i;
clk_prepare_enable(csi2rx->p_clk);
+ reset_control_assert(csi2rx->sys_rst);
clk_disable_unprepare(csi2rx->sys_clk);
for (i = 0; i < csi2rx->max_streams; i++) {
writel(0, csi2rx->base + CSI2RX_STREAM_CTRL_REG(i));
+ reset_control_assert(csi2rx->pixel_rst[i]);
clk_disable_unprepare(csi2rx->pixel_clk[i]);
}
+ reset_control_assert(csi2rx->p_rst);
clk_disable_unprepare(csi2rx->p_clk);
if (v4l2_subdev_call(csi2rx->source_subdev, video, s_stream, false))
dev_warn(csi2rx->dev, "Couldn't disable our subdev\n");
+
+ if (csi2rx->dphy) {
+ writel(0, csi2rx->base + CSI2RX_DPHY_LANE_CTRL_REG);
+
+ if (phy_power_off(csi2rx->dphy))
+ dev_warn(csi2rx->dev, "Couldn't power off DPHY\n");
+ }
}
static int csi2rx_s_stream(struct v4l2_subdev *subdev, int enable)
return ret;
}
+static const struct v4l2_subdev_core_ops csi2rx_core_ops = {
+ .s_power = csi2rx_s_power,
+};
+
static const struct v4l2_subdev_video_ops csi2rx_video_ops = {
.s_stream = csi2rx_s_stream,
};
static const struct v4l2_subdev_ops csi2rx_subdev_ops = {
+ .core = &csi2rx_core_ops,
.video = &csi2rx_video_ops,
};
{
struct v4l2_subdev *subdev = notifier->sd;
struct csi2rx_priv *csi2rx = v4l2_subdev_to_csi2rx(subdev);
+ struct v4l2_device *v4l2_dev;
+ int ret;
csi2rx->source_pad = media_entity_get_fwnode_pad(&s_subdev->entity,
s_subdev->fwnode,
dev_dbg(csi2rx->dev, "Bound %s pad: %d\n", s_subdev->name,
csi2rx->source_pad);
+ /* ensure source subdev register subdev node */
+ v4l2_dev = notifier->v4l2_dev ? notifier->v4l2_dev :
+ notifier->parent->v4l2_dev;
+ if (v4l2_dev) {
+ ret = v4l2_device_register_subdev_nodes(v4l2_dev);
+ if (ret < 0)
+ return ret;
+ }
+
return media_create_pad_link(&csi2rx->source_subdev->entity,
csi2rx->source_pad,
&csi2rx->subdev.entity, 0,
return PTR_ERR(csi2rx->p_clk);
}
+ csi2rx->sys_rst =
+ devm_reset_control_get_optional_exclusive(&pdev->dev,
+ "sys_rst");
+ if (IS_ERR(csi2rx->sys_rst))
+ return PTR_ERR(csi2rx->sys_rst);
+
+ csi2rx->p_rst =
+ devm_reset_control_get_optional_exclusive(&pdev->dev, "p_rst");
+ if (IS_ERR(csi2rx->p_rst))
+ return PTR_ERR(csi2rx->p_rst);
+
csi2rx->dphy = devm_phy_optional_get(&pdev->dev, "dphy");
if (IS_ERR(csi2rx->dphy)) {
dev_err(&pdev->dev, "Couldn't get external D-PHY\n");
return PTR_ERR(csi2rx->dphy);
}
- /*
- * FIXME: Once we'll have external D-PHY support, the check
- * will need to be removed.
- */
- if (csi2rx->dphy) {
- dev_err(&pdev->dev, "External D-PHY not supported yet\n");
- return -EINVAL;
- }
-
ret = clk_prepare_enable(csi2rx->p_clk);
if (ret) {
dev_err(&pdev->dev, "Couldn't prepare and enable P clock\n");
* FIXME: Once we'll have internal D-PHY support, the check
* will need to be removed.
*/
- if (csi2rx->has_internal_dphy) {
+ if (!csi2rx->dphy && csi2rx->has_internal_dphy) {
dev_err(&pdev->dev, "Internal D-PHY not supported yet\n");
return -EINVAL;
}
for (i = 0; i < csi2rx->max_streams; i++) {
char clk_name[16];
+ char rst_name[16];
snprintf(clk_name, sizeof(clk_name), "pixel_if%u_clk", i);
csi2rx->pixel_clk[i] = devm_clk_get(&pdev->dev, clk_name);
dev_err(&pdev->dev, "Couldn't get clock %s\n", clk_name);
return PTR_ERR(csi2rx->pixel_clk[i]);
}
+
+ snprintf(rst_name, sizeof(rst_name), "pixel_if%u_rst", i);
+ csi2rx->pixel_rst[i] =
+ devm_reset_control_get_optional_exclusive(&pdev->dev,
+ rst_name);
+ if (IS_ERR(csi2rx->pixel_rst[i]))
+ return PTR_ERR(csi2rx->pixel_rst[i]);
}
return 0;
csi2rx = kzalloc(sizeof(*csi2rx), GFP_KERNEL);
if (!csi2rx)
return -ENOMEM;
+ csi2rx->platform_info = of_device_get_match_data(&pdev->dev);
platform_set_drvdata(pdev, csi2rx);
csi2rx->dev = &pdev->dev;
mutex_init(&csi2rx->lock);
csi2rx->subdev.dev = &pdev->dev;
v4l2_subdev_init(&csi2rx->subdev, &csi2rx_subdev_ops);
v4l2_set_subdevdata(&csi2rx->subdev, &pdev->dev);
+ csi2rx->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
snprintf(csi2rx->subdev.name, V4L2_SUBDEV_NAME_SIZE, "%s.%s",
KBUILD_MODNAME, dev_name(&pdev->dev));
dev_info(&pdev->dev,
"Probed CSI2RX with %u/%u lanes, %u streams, %s D-PHY\n",
csi2rx->num_lanes, csi2rx->max_lanes, csi2rx->max_streams,
+ csi2rx->dphy ? "external" :
csi2rx->has_internal_dphy ? "internal" : "no");
return 0;
return 0;
}
+static const struct csi2rx_platform_info stf_jh7110_info = {
+ .sys_clk_rate = 297000000,
+};
+
static const struct of_device_id csi2rx_of_table[] = {
{ .compatible = "cdns,csi2rx" },
+ { .compatible = "starfive,jh7110-csi2rx", .data = &stf_jh7110_info },
{ },
};
MODULE_DEVICE_TABLE(of, csi2rx_of_table);