lkml.org 
[lkml]   [2023]   [Mar]   [20]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
/
Date
SubjectRe: [PATCH v2 4/6] media: cadence: Add support for external dphy and JH7110 SoC
From


On 2023/3/12 19:33, Laurent Pinchart wrote:
> Hi Jack,
>
> Thank you for the patch.
>
> On Fri, Mar 10, 2023 at 08:05:51PM +0800, Jack Zhu wrote:
>> Add support for external MIPI D-PHY and Starfive JH7110 SoC which
>> has the cadence csi2 receiver.
>
> This patch bundles multiple changes that should be split into their own
> patches. I'll comment on that below.

Thank you for your suggestion and comments. I'll split the patch.

>
>> Signed-off-by: Jack Zhu <jack.zhu@starfivetech.com>
>> ---
>> drivers/media/platform/cadence/cdns-csi2rx.c | 273 ++++++++++++++++++-
>> 1 file changed, 263 insertions(+), 10 deletions(-)
>>
>> diff --git a/drivers/media/platform/cadence/cdns-csi2rx.c b/drivers/media/platform/cadence/cdns-csi2rx.c
>> index cc3ebb0d96f6..7e7b096869fc 100644
>> --- a/drivers/media/platform/cadence/cdns-csi2rx.c
>> +++ b/drivers/media/platform/cadence/cdns-csi2rx.c
>> @@ -10,9 +10,11 @@
>> #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>
>> @@ -30,6 +32,12 @@
>> #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)
>
> .s_power() is deprecated, please drop it. You should use runtime PM
> instead (with autosuspend). In .s_stream(), you should call
> pm_runtime_resume_and_get() when starting streaming, and
> pm_runtime_put_autosuspend() when stopping streaming.
>

OK, will drop .s_power(). The csi2rx will do not call phy_init()/
phy_exit(). The work of phy_init()/phy_exit() is integrated into PHY
driver.

>> +{
>> + 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)
>
> Support for external DPHYs should be split to a patch of its own.

OK, will split the patch.

>
>> +{
>> + 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);
>
> Don't call .get_fmt() on the source. A subdev driver should only look at
> its own formats. As the format on the source pad of the source must
> match the format on the sink pad of this subdev when the pipeline is
> valid, you can use the format on the sink pad here.

OK, will drop. This step of work is not necessary.

>
>> + 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);
>
> Support for reset controllers should also be split to a patch of its
> own.

OK, will split the patch according to logic change.

>
>> +
>> 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);
>
> Same as above regarding the format.

OK, will fix.

>
>> + if (ret)
>> + dev_warn(csi2rx->dev, "Couldn't get format\n");
>> +
>> + dt = csi2rx_get_dt(sd_fmt.format.code);
>> + if (!dt)
>
> When you reach this point, the format should be guaranteed to be valid
> as .set_fmt() will have adjusted it. There should thus be no need to
> handle errors.
>
>> + 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));
>
> Support for CSI-2 DT should be split to a patch of its own.

CSI-2 DT is not necessary. I will drop the relevant code.

>
>> +
>> 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 @@ 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,
>> };
>>
>> @@ -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);
>
> This belongs to the main driver (the camss in your case), not this
> driver.

OK, will fix.

>
>> + 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,
>
> This sounds like something that would be better handled through the
> assigned-clock-rates property in DT.

OK, will use assigned-clock-rates property in DT.

>
>> +};
>> +
>> static const struct of_device_id csi2rx_of_table[] = {
>> { .compatible = "cdns,csi2rx" },
>> + { .compatible = "starfive,jh7110-csi2rx", .data = &stf_jh7110_info },
>
> I would move SoC-specific compatible strings before the generic one.

OK, will move it before the generic one.

> Maxime, is there a need to keep the generic compatible string now that
> we have SoC-specific support ?

The existing bindings state

- compatible: must be set to "cdns,csi2rx" and an SoC-specific compatible

It might be better to keep the generic compatible string.

>
>> { },
>> };
>> MODULE_DEVICE_TABLE(of, csi2rx_of_table);
>

\
 
 \ /
  Last update: 2023-03-27 01:08    [W:0.108 / U:0.712 seconds]
©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site