media: cadence: Add support for external dphy and JH7110 SoC
authorJack Zhu <jack.zhu@starfivetech.com>
Fri, 10 Mar 2023 12:05:51 +0000 (20:05 +0800)
committerJaehoon Chung <jh80.chung@samsung.com>
Mon, 24 Jul 2023 23:24:44 +0000 (08:24 +0900)
Add support for external MIPI D-PHY and Starfive JH7110 SoC which
has the cadence csi2 receiver.

Signed-off-by: Jack Zhu <jack.zhu@starfivetech.com>
drivers/media/platform/cadence/cdns-csi2rx.c

index cc3ebb0..7e7b096 100644 (file)
 #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)
@@ -37,6 +45,7 @@
 
 #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)
@@ -54,8 +63,19 @@ enum csi2rx_pads {
        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;
 
        /*
@@ -68,6 +88,9 @@ struct csi2rx_priv {
        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];
@@ -83,14 +106,100 @@ struct csi2rx_priv {
        /* 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,
@@ -101,17 +210,70 @@ static void csi2rx_reset(struct csi2rx_priv *csi2rx)
        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;
@@ -139,6 +301,29 @@ static int csi2rx_start(struct csi2rx_priv *csi2rx)
        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.
@@ -154,6 +339,8 @@ static int csi2rx_start(struct csi2rx_priv *csi2rx)
                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));
 
@@ -161,6 +348,11 @@ static int csi2rx_start(struct csi2rx_priv *csi2rx)
                       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));
        }
@@ -169,10 +361,27 @@ static int csi2rx_start(struct csi2rx_priv *csi2rx)
        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]);
@@ -188,18 +397,28 @@ static void csi2rx_stop(struct csi2rx_priv *csi2rx)
        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)
@@ -236,11 +455,16 @@ out:
        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,
 };
 
@@ -250,6 +474,8 @@ static int csi2rx_async_bound(struct v4l2_async_notifier *notifier,
 {
        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,
@@ -265,6 +491,15 @@ static int csi2rx_async_bound(struct v4l2_async_notifier *notifier,
        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,
@@ -299,21 +534,23 @@ static int csi2rx_get_resources(struct csi2rx_priv *csi2rx,
                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");
@@ -343,13 +580,14 @@ static int csi2rx_get_resources(struct csi2rx_priv *csi2rx,
         * 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);
@@ -357,6 +595,13 @@ static int csi2rx_get_resources(struct csi2rx_priv *csi2rx,
                        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;
@@ -425,6 +670,7 @@ static int csi2rx_probe(struct platform_device *pdev)
        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);
@@ -441,6 +687,7 @@ static int csi2rx_probe(struct platform_device *pdev)
        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));
 
@@ -462,6 +709,7 @@ static int csi2rx_probe(struct platform_device *pdev)
        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;
@@ -483,8 +731,13 @@ static int csi2rx_remove(struct platform_device *pdev)
        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);