root/drivers/phy/rockchip/phy-rockchip-inno-csidphy.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Rockchip MIPI RX Innosilicon DPHY driver
 *
 * Copyright (C) 2021 Fuzhou Rockchip Electronics Co., Ltd.
 */

#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/phy/phy.h>
#include <linux/phy/phy-mipi-dphy.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/reset.h>

/* GRF */
#define RK1808_GRF_PD_VI_CON_OFFSET     0x0430

#define RK3326_GRF_PD_VI_CON_OFFSET     0x0430

#define RK3368_GRF_SOC_CON6_OFFSET      0x0418

#define RK3568_GRF_VI_CON0              0x0340
#define RK3568_GRF_VI_CON1              0x0344

#define RK3588_CSIDPHY_GRF_CON0         0x0000

/* PHY */
#define CSIDPHY_CTRL_LANE_ENABLE                0x00
#define CSIDPHY_CTRL_LANE_ENABLE_CK             BIT(6)
#define CSIDPHY_CTRL_LANE_ENABLE_MASK           GENMASK(5, 2)
#define CSIDPHY_CTRL_LANE_ENABLE_UNDEFINED      BIT(0)

/* not present on all variants */
#define CSIDPHY_CTRL_PWRCTL                     0x04
#define CSIDPHY_CTRL_PWRCTL_UNDEFINED           GENMASK(7, 5)
#define CSIDPHY_CTRL_PWRCTL_SYNCRST             BIT(2)
#define CSIDPHY_CTRL_PWRCTL_LDO_PD              BIT(1)
#define CSIDPHY_CTRL_PWRCTL_PLL_PD              BIT(0)

#define CSIDPHY_CTRL_DIG_RST                    0x80
#define CSIDPHY_CTRL_DIG_RST_UNDEFINED          0x1e
#define CSIDPHY_CTRL_DIG_RST_RESET              BIT(0)

/* offset after ths_settle_offset */
#define CSIDPHY_CLK_THS_SETTLE                  0
#define CSIDPHY_LANE_THS_SETTLE(n)              (((n) + 1) * 0x80)
#define CSIDPHY_THS_SETTLE_MASK                 GENMASK(6, 0)

/* offset after calib_offset */
#define CSIDPHY_CLK_CALIB_EN                    0
#define CSIDPHY_LANE_CALIB_EN(n)                (((n) + 1) * 0x80)
#define CSIDPHY_CALIB_EN                        BIT(7)

/* Configure the count time of the THS-SETTLE by protocol. */
#define RK1808_CSIDPHY_CLK_WR_THS_SETTLE        0x160
#define RK3326_CSIDPHY_CLK_WR_THS_SETTLE        0x100
#define RK3368_CSIDPHY_CLK_WR_THS_SETTLE        0x100
#define RK3568_CSIDPHY_CLK_WR_THS_SETTLE        0x160

/* Calibration reception enable */
#define RK1808_CSIDPHY_CLK_CALIB_EN             0x168
#define RK3568_CSIDPHY_CLK_CALIB_EN             0x168

#define RESETS_MAX                              2

/*
 * The higher 16-bit of this register is used for write protection
 * only if BIT(x + 16) set to 1 the BIT(x) can be written.
 */
#define HIWORD_UPDATE(val, mask, shift) \
                ((val) << (shift) | (mask) << ((shift) + 16))

#define HZ_TO_MHZ(freq)                         div_u64(freq, 1000 * 1000)

enum dphy_reg_id {
        /* rk1808 & rk3326 */
        GRF_DPHY_CSIPHY_FORCERXMODE,
        GRF_DPHY_CSIPHY_CLKLANE_EN,
        GRF_DPHY_CSIPHY_DATALANE_EN,
};

struct dphy_reg {
        u32 offset;
        u32 mask;
        u32 shift;
        u8 valid;
};

#define PHY_REG(_offset, _width, _shift) \
        { .offset = _offset, .mask = BIT(_width) - 1, .shift = _shift, .valid = 1, }

static const struct dphy_reg rk1808_grf_dphy_regs[] = {
        [GRF_DPHY_CSIPHY_FORCERXMODE] = PHY_REG(RK1808_GRF_PD_VI_CON_OFFSET, 4, 0),
        [GRF_DPHY_CSIPHY_CLKLANE_EN] = PHY_REG(RK1808_GRF_PD_VI_CON_OFFSET, 1, 8),
        [GRF_DPHY_CSIPHY_DATALANE_EN] = PHY_REG(RK1808_GRF_PD_VI_CON_OFFSET, 4, 4),
};

static const struct dphy_reg rk3326_grf_dphy_regs[] = {
        [GRF_DPHY_CSIPHY_FORCERXMODE] = PHY_REG(RK3326_GRF_PD_VI_CON_OFFSET, 4, 0),
        [GRF_DPHY_CSIPHY_CLKLANE_EN] = PHY_REG(RK3326_GRF_PD_VI_CON_OFFSET, 1, 8),
        [GRF_DPHY_CSIPHY_DATALANE_EN] = PHY_REG(RK3326_GRF_PD_VI_CON_OFFSET, 4, 4),
};

static const struct dphy_reg rk3368_grf_dphy_regs[] = {
        [GRF_DPHY_CSIPHY_FORCERXMODE] = PHY_REG(RK3368_GRF_SOC_CON6_OFFSET, 4, 8),
};

static const struct dphy_reg rk3568_grf_dphy_regs[] = {
        [GRF_DPHY_CSIPHY_FORCERXMODE] = PHY_REG(RK3568_GRF_VI_CON0, 4, 0),
        [GRF_DPHY_CSIPHY_DATALANE_EN] = PHY_REG(RK3568_GRF_VI_CON0, 4, 4),
        [GRF_DPHY_CSIPHY_CLKLANE_EN] = PHY_REG(RK3568_GRF_VI_CON0, 1, 8),
};

static const struct dphy_reg rk3588_grf_dphy_regs[] = {
        [GRF_DPHY_CSIPHY_FORCERXMODE] = PHY_REG(RK3588_CSIDPHY_GRF_CON0, 4, 0),
        [GRF_DPHY_CSIPHY_DATALANE_EN] = PHY_REG(RK3588_CSIDPHY_GRF_CON0, 4, 4),
        [GRF_DPHY_CSIPHY_CLKLANE_EN] = PHY_REG(RK3588_CSIDPHY_GRF_CON0, 1, 8),
};

struct hsfreq_range {
        u32 range_h;
        u8 cfg_bit;
};

struct dphy_drv_data {
        int pwrctl_offset;
        int ths_settle_offset;
        int calib_offset;
        const struct hsfreq_range *hsfreq_ranges;
        int num_hsfreq_ranges;
        const struct dphy_reg *grf_regs;
        const char *const *resets;
        unsigned int resets_num;
};

struct rockchip_inno_csidphy {
        struct device *dev;
        void __iomem *phy_base;
        struct clk *pclk;
        struct regmap *grf;
        struct reset_control_bulk_data resets[RESETS_MAX];
        unsigned int resets_num;
        const struct dphy_drv_data *drv_data;
        struct phy_configure_opts_mipi_dphy config;
        u8 hsfreq;
};

static inline void write_grf_reg(struct rockchip_inno_csidphy *priv,
                                 int index, u8 value)
{
        const struct dphy_drv_data *drv_data = priv->drv_data;
        const struct dphy_reg *reg = &drv_data->grf_regs[index];

        if (reg->valid)
                regmap_write(priv->grf, reg->offset,
                             HIWORD_UPDATE(value, reg->mask, reg->shift));
}

/* These tables must be sorted by .range_h ascending. */
static const struct hsfreq_range rk1808_mipidphy_hsfreq_ranges[] = {
        { 109, 0x02}, { 149, 0x03}, { 199, 0x06}, { 249, 0x06},
        { 299, 0x06}, { 399, 0x08}, { 499, 0x0b}, { 599, 0x0e},
        { 699, 0x10}, { 799, 0x12}, { 999, 0x16}, {1199, 0x1e},
        {1399, 0x23}, {1599, 0x2d}, {1799, 0x32}, {1999, 0x37},
        {2199, 0x3c}, {2399, 0x41}, {2499, 0x46}
};

static const struct hsfreq_range rk3326_mipidphy_hsfreq_ranges[] = {
        { 109, 0x00}, { 149, 0x01}, { 199, 0x02}, { 249, 0x03},
        { 299, 0x04}, { 399, 0x05}, { 499, 0x06}, { 599, 0x07},
        { 699, 0x08}, { 799, 0x09}, { 899, 0x0a}, {1099, 0x0b},
        {1249, 0x0c}, {1349, 0x0d}, {1500, 0x0e}
};

static const struct hsfreq_range rk3368_mipidphy_hsfreq_ranges[] = {
        { 109, 0x00}, { 149, 0x01}, { 199, 0x02}, { 249, 0x03},
        { 299, 0x04}, { 399, 0x05}, { 499, 0x06}, { 599, 0x07},
        { 699, 0x08}, { 799, 0x09}, { 899, 0x0a}, {1099, 0x0b},
        {1249, 0x0c}, {1349, 0x0d}, {1500, 0x0e}
};

static const char *const rk3368_reset_names[] = {
        "apb"
};

static const char *const rk3588_reset_names[] = {
        "apb",
        "phy"
};

static void rockchip_inno_csidphy_ths_settle(struct rockchip_inno_csidphy *priv,
                                             int hsfreq, int offset)
{
        const struct dphy_drv_data *drv_data = priv->drv_data;
        u32 val;

        val = readl(priv->phy_base + drv_data->ths_settle_offset + offset);
        val &= ~CSIDPHY_THS_SETTLE_MASK;
        val |= hsfreq;
        writel(val, priv->phy_base + drv_data->ths_settle_offset + offset);
}

static int rockchip_inno_csidphy_configure(struct phy *phy,
                                           union phy_configure_opts *opts)
{
        struct rockchip_inno_csidphy *priv = phy_get_drvdata(phy);
        const struct dphy_drv_data *drv_data = priv->drv_data;
        struct phy_configure_opts_mipi_dphy *config = &opts->mipi_dphy;
        unsigned int hsfreq = 0;
        unsigned int i;
        u64 data_rate_mbps;
        int ret;

        /* pass with phy_mipi_dphy_get_default_config (with pixel rate?) */
        ret = phy_mipi_dphy_config_validate(config);
        if (ret)
                return ret;

        data_rate_mbps = HZ_TO_MHZ(config->hs_clk_rate);

        dev_dbg(priv->dev, "lanes %d - data_rate_mbps %llu\n",
                config->lanes, data_rate_mbps);
        for (i = 0; i < drv_data->num_hsfreq_ranges; i++) {
                if (drv_data->hsfreq_ranges[i].range_h >= data_rate_mbps) {
                        hsfreq = drv_data->hsfreq_ranges[i].cfg_bit;
                        break;
                }
        }
        if (!hsfreq)
                return -EINVAL;

        priv->hsfreq = hsfreq;
        priv->config = *config;
        return 0;
}

static int rockchip_inno_csidphy_power_on(struct phy *phy)
{
        struct rockchip_inno_csidphy *priv = phy_get_drvdata(phy);
        const struct dphy_drv_data *drv_data = priv->drv_data;
        u64 data_rate_mbps = HZ_TO_MHZ(priv->config.hs_clk_rate);
        u32 val;
        int ret, i;

        ret = clk_enable(priv->pclk);
        if (ret < 0)
                return ret;

        ret = pm_runtime_resume_and_get(priv->dev);
        if (ret < 0) {
                clk_disable(priv->pclk);
                return ret;
        }

        /* phy start */
        if (drv_data->pwrctl_offset >= 0)
                writel(CSIDPHY_CTRL_PWRCTL_UNDEFINED |
                       CSIDPHY_CTRL_PWRCTL_SYNCRST,
                       priv->phy_base + drv_data->pwrctl_offset);

        /* set data lane num and enable clock lane */
        val = FIELD_PREP(CSIDPHY_CTRL_LANE_ENABLE_MASK, GENMASK(priv->config.lanes - 1, 0)) |
              FIELD_PREP(CSIDPHY_CTRL_LANE_ENABLE_CK, 1) |
              FIELD_PREP(CSIDPHY_CTRL_LANE_ENABLE_UNDEFINED, 1);
        writel(val, priv->phy_base + CSIDPHY_CTRL_LANE_ENABLE);

        /* Reset dphy analog part */
        if (drv_data->pwrctl_offset >= 0)
                writel(CSIDPHY_CTRL_PWRCTL_UNDEFINED,
                       priv->phy_base + drv_data->pwrctl_offset);
        usleep_range(500, 1000);

        /* Reset dphy digital part */
        writel(CSIDPHY_CTRL_DIG_RST_UNDEFINED,
               priv->phy_base + CSIDPHY_CTRL_DIG_RST);
        writel(CSIDPHY_CTRL_DIG_RST_UNDEFINED + CSIDPHY_CTRL_DIG_RST_RESET,
               priv->phy_base + CSIDPHY_CTRL_DIG_RST);

        /* not into receive mode/wait stopstate */
        write_grf_reg(priv, GRF_DPHY_CSIPHY_FORCERXMODE, 0x0);

        /* enable calibration */
        if (data_rate_mbps > 1500 && drv_data->calib_offset >= 0) {
                writel(CSIDPHY_CALIB_EN,
                       priv->phy_base + drv_data->calib_offset +
                                        CSIDPHY_CLK_CALIB_EN);
                for (i = 0; i < priv->config.lanes; i++)
                        writel(CSIDPHY_CALIB_EN,
                               priv->phy_base + drv_data->calib_offset +
                                                CSIDPHY_LANE_CALIB_EN(i));
        }

        rockchip_inno_csidphy_ths_settle(priv, priv->hsfreq,
                                         CSIDPHY_CLK_THS_SETTLE);
        for (i = 0; i < priv->config.lanes; i++)
                rockchip_inno_csidphy_ths_settle(priv, priv->hsfreq,
                                                 CSIDPHY_LANE_THS_SETTLE(i));

        write_grf_reg(priv, GRF_DPHY_CSIPHY_CLKLANE_EN, 0x1);
        write_grf_reg(priv, GRF_DPHY_CSIPHY_DATALANE_EN,
                      GENMASK(priv->config.lanes - 1, 0));

        return 0;
}

static int rockchip_inno_csidphy_power_off(struct phy *phy)
{
        struct rockchip_inno_csidphy *priv = phy_get_drvdata(phy);
        const struct dphy_drv_data *drv_data = priv->drv_data;

        /* disable all lanes */
        writel(CSIDPHY_CTRL_LANE_ENABLE_UNDEFINED,
               priv->phy_base + CSIDPHY_CTRL_LANE_ENABLE);

        /* disable pll and ldo */
        if (drv_data->pwrctl_offset >= 0)
                writel(CSIDPHY_CTRL_PWRCTL_UNDEFINED |
                       CSIDPHY_CTRL_PWRCTL_LDO_PD |
                       CSIDPHY_CTRL_PWRCTL_PLL_PD,
                       priv->phy_base + drv_data->pwrctl_offset);
        usleep_range(500, 1000);

        pm_runtime_put(priv->dev);
        clk_disable(priv->pclk);

        return 0;
}

static int rockchip_inno_csidphy_init(struct phy *phy)
{
        struct rockchip_inno_csidphy *priv = phy_get_drvdata(phy);

        return clk_prepare(priv->pclk);
}

static int rockchip_inno_csidphy_exit(struct phy *phy)
{
        struct rockchip_inno_csidphy *priv = phy_get_drvdata(phy);

        clk_unprepare(priv->pclk);

        return 0;
}

static const struct phy_ops rockchip_inno_csidphy_ops = {
        .power_on       = rockchip_inno_csidphy_power_on,
        .power_off      = rockchip_inno_csidphy_power_off,
        .init           = rockchip_inno_csidphy_init,
        .exit           = rockchip_inno_csidphy_exit,
        .configure      = rockchip_inno_csidphy_configure,
        .owner          = THIS_MODULE,
};

static const struct dphy_drv_data rk1808_mipidphy_drv_data = {
        .pwrctl_offset = -1,
        .ths_settle_offset = RK1808_CSIDPHY_CLK_WR_THS_SETTLE,
        .calib_offset = RK1808_CSIDPHY_CLK_CALIB_EN,
        .hsfreq_ranges = rk1808_mipidphy_hsfreq_ranges,
        .num_hsfreq_ranges = ARRAY_SIZE(rk1808_mipidphy_hsfreq_ranges),
        .grf_regs = rk1808_grf_dphy_regs,
        .resets = rk3368_reset_names,
        .resets_num = ARRAY_SIZE(rk3368_reset_names),
};

static const struct dphy_drv_data rk3326_mipidphy_drv_data = {
        .pwrctl_offset = CSIDPHY_CTRL_PWRCTL,
        .ths_settle_offset = RK3326_CSIDPHY_CLK_WR_THS_SETTLE,
        .calib_offset = -1,
        .hsfreq_ranges = rk3326_mipidphy_hsfreq_ranges,
        .num_hsfreq_ranges = ARRAY_SIZE(rk3326_mipidphy_hsfreq_ranges),
        .grf_regs = rk3326_grf_dphy_regs,
        .resets = rk3368_reset_names,
        .resets_num = ARRAY_SIZE(rk3368_reset_names),
};

static const struct dphy_drv_data rk3368_mipidphy_drv_data = {
        .pwrctl_offset = CSIDPHY_CTRL_PWRCTL,
        .ths_settle_offset = RK3368_CSIDPHY_CLK_WR_THS_SETTLE,
        .calib_offset = -1,
        .hsfreq_ranges = rk3368_mipidphy_hsfreq_ranges,
        .num_hsfreq_ranges = ARRAY_SIZE(rk3368_mipidphy_hsfreq_ranges),
        .grf_regs = rk3368_grf_dphy_regs,
        .resets = rk3368_reset_names,
        .resets_num = ARRAY_SIZE(rk3368_reset_names),
};

static const struct dphy_drv_data rk3568_mipidphy_drv_data = {
        .pwrctl_offset = -1,
        .ths_settle_offset = RK3568_CSIDPHY_CLK_WR_THS_SETTLE,
        .calib_offset = RK3568_CSIDPHY_CLK_CALIB_EN,
        .hsfreq_ranges = rk1808_mipidphy_hsfreq_ranges,
        .num_hsfreq_ranges = ARRAY_SIZE(rk1808_mipidphy_hsfreq_ranges),
        .grf_regs = rk3568_grf_dphy_regs,
        .resets = rk3368_reset_names,
        .resets_num = ARRAY_SIZE(rk3368_reset_names),
};

static const struct dphy_drv_data rk3588_mipidphy_drv_data = {
        .pwrctl_offset = -1,
        .ths_settle_offset = RK3568_CSIDPHY_CLK_WR_THS_SETTLE,
        .calib_offset = RK3568_CSIDPHY_CLK_CALIB_EN,
        .hsfreq_ranges = rk1808_mipidphy_hsfreq_ranges,
        .num_hsfreq_ranges = ARRAY_SIZE(rk1808_mipidphy_hsfreq_ranges),
        .grf_regs = rk3588_grf_dphy_regs,
        .resets = rk3588_reset_names,
        .resets_num = ARRAY_SIZE(rk3588_reset_names),
};

static const struct of_device_id rockchip_inno_csidphy_match_id[] = {
        {
                .compatible = "rockchip,px30-csi-dphy",
                .data = &rk3326_mipidphy_drv_data,
        },
        {
                .compatible = "rockchip,rk1808-csi-dphy",
                .data = &rk1808_mipidphy_drv_data,
        },
        {
                .compatible = "rockchip,rk3326-csi-dphy",
                .data = &rk3326_mipidphy_drv_data,
        },
        {
                .compatible = "rockchip,rk3368-csi-dphy",
                .data = &rk3368_mipidphy_drv_data,
        },
        {
                .compatible = "rockchip,rk3568-csi-dphy",
                .data = &rk3568_mipidphy_drv_data,
        },
        {
                .compatible = "rockchip,rk3588-csi-dphy",
                .data = &rk3588_mipidphy_drv_data,
        },
        {}
};
MODULE_DEVICE_TABLE(of, rockchip_inno_csidphy_match_id);

static int rockchip_inno_csidphy_probe(struct platform_device *pdev)
{
        struct rockchip_inno_csidphy *priv;
        struct device *dev = &pdev->dev;
        struct phy_provider *phy_provider;
        struct phy *phy;
        int ret;

        priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
        if (!priv)
                return -ENOMEM;

        priv->dev = dev;
        platform_set_drvdata(pdev, priv);

        priv->drv_data = of_device_get_match_data(dev);
        if (!priv->drv_data) {
                dev_err(dev, "Can't find device data\n");
                return -ENODEV;
        }

        priv->grf = syscon_regmap_lookup_by_phandle(dev->of_node,
                                                    "rockchip,grf");
        if (IS_ERR(priv->grf)) {
                dev_err(dev, "Can't find GRF syscon\n");
                return PTR_ERR(priv->grf);
        }

        priv->phy_base = devm_platform_ioremap_resource(pdev, 0);
        if (IS_ERR(priv->phy_base))
                return PTR_ERR(priv->phy_base);

        priv->pclk = devm_clk_get(dev, "pclk");
        if (IS_ERR(priv->pclk)) {
                dev_err(dev, "failed to get pclk\n");
                return PTR_ERR(priv->pclk);
        }

        if (priv->drv_data->resets_num > RESETS_MAX) {
                dev_err(dev, "invalid number of resets\n");
                return -EINVAL;
        }
        priv->resets_num = priv->drv_data->resets_num;
        for (unsigned int i = 0; i < priv->resets_num; i++)
                priv->resets[i].id = priv->drv_data->resets[i];
        ret = devm_reset_control_bulk_get_exclusive(dev, priv->resets_num,
                                                    priv->resets);
        if (ret) {
                dev_err(dev, "failed to get system reset control\n");
                return ret;
        }

        phy = devm_phy_create(dev, NULL, &rockchip_inno_csidphy_ops);
        if (IS_ERR(phy)) {
                dev_err(dev, "failed to create phy\n");
                return PTR_ERR(phy);
        }

        phy_set_drvdata(phy, priv);

        phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
        if (IS_ERR(phy_provider)) {
                dev_err(dev, "failed to register phy provider\n");
                return PTR_ERR(phy_provider);
        }

        pm_runtime_enable(dev);

        return 0;
}

static void rockchip_inno_csidphy_remove(struct platform_device *pdev)
{
        struct rockchip_inno_csidphy *priv = platform_get_drvdata(pdev);

        pm_runtime_disable(priv->dev);
}

static struct platform_driver rockchip_inno_csidphy_driver = {
        .driver = {
                .name = "rockchip-inno-csidphy",
                .of_match_table = rockchip_inno_csidphy_match_id,
        },
        .probe = rockchip_inno_csidphy_probe,
        .remove = rockchip_inno_csidphy_remove,
};

module_platform_driver(rockchip_inno_csidphy_driver);
MODULE_AUTHOR("Heiko Stuebner <heiko.stuebner@theobroma-systems.com>");
MODULE_DESCRIPTION("Rockchip MIPI Innosilicon CSI-DPHY driver");
MODULE_LICENSE("GPL v2");