root/drivers/phy/cadence/cdns-dphy.c
// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright: 2017-2018 Cadence Design Systems, Inc.
 */

#include <linux/bitfield.h>
#include <linux/bitops.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/reset.h>

#include <linux/phy/phy.h>
#include <linux/phy/phy-mipi-dphy.h>

#define REG_WAKEUP_TIME_NS              800
#define DPHY_PLL_RATE_HZ                108000000
#define POLL_TIMEOUT_US                 1000

/* DPHY registers */
#define DPHY_PMA_CMN(reg)               (reg)
#define DPHY_PMA_LCLK(reg)              (0x100 + (reg))
#define DPHY_PMA_LDATA(lane, reg)       (0x200 + ((lane) * 0x100) + (reg))
#define DPHY_PMA_RCLK(reg)              (0x600 + (reg))
#define DPHY_PMA_RDATA(lane, reg)       (0x700 + ((lane) * 0x100) + (reg))
#define DPHY_PCS(reg)                   (0xb00 + (reg))

#define DPHY_CMN_SSM                    DPHY_PMA_CMN(0x20)
#define DPHY_CMN_SSM_EN                 BIT(0)
#define DPHY_CMN_SSM_CAL_WAIT_TIME      GENMASK(8, 1)
#define DPHY_CMN_TX_MODE_EN             BIT(9)

#define DPHY_CMN_PWM                    DPHY_PMA_CMN(0x40)
#define DPHY_CMN_PWM_DIV(x)             ((x) << 20)
#define DPHY_CMN_PWM_LOW(x)             ((x) << 10)
#define DPHY_CMN_PWM_HIGH(x)            (x)

#define DPHY_CMN_FBDIV                  DPHY_PMA_CMN(0x4c)
#define DPHY_CMN_FBDIV_VAL(low, high)   (((high) << 11) | ((low) << 22))
#define DPHY_CMN_FBDIV_FROM_REG         (BIT(10) | BIT(21))

#define DPHY_CMN_OPIPDIV                DPHY_PMA_CMN(0x50)
#define DPHY_CMN_IPDIV_FROM_REG         BIT(0)
#define DPHY_CMN_IPDIV(x)               ((x) << 1)
#define DPHY_CMN_OPDIV_FROM_REG         BIT(6)
#define DPHY_CMN_OPDIV(x)               ((x) << 7)

#define DPHY_BAND_CFG                   DPHY_PCS(0x0)
#define DPHY_BAND_CFG_LEFT_BAND         GENMASK(4, 0)
#define DPHY_BAND_CFG_RIGHT_BAND        GENMASK(9, 5)

#define DPHY_PSM_CFG                    DPHY_PCS(0x4)
#define DPHY_PSM_CFG_FROM_REG           BIT(0)
#define DPHY_PSM_CLK_DIV(x)             ((x) << 1)

#define DPHY_TX_J721E_WIZ_PLL_CTRL      0xF04
#define DPHY_TX_J721E_WIZ_STATUS        0xF08
#define DPHY_TX_J721E_WIZ_RST_CTRL      0xF0C
#define DPHY_TX_J721E_WIZ_PSM_FREQ      0xF10

#define DPHY_TX_J721E_WIZ_IPDIV         GENMASK(4, 0)
#define DPHY_TX_J721E_WIZ_OPDIV         GENMASK(13, 8)
#define DPHY_TX_J721E_WIZ_FBDIV         GENMASK(25, 16)
#define DPHY_TX_J721E_WIZ_LANE_RSTB     BIT(31)
#define DPHY_TX_WIZ_PLL_LOCK            BIT(31)
#define DPHY_TX_WIZ_O_CMN_READY         BIT(31)

struct cdns_dphy_cfg {
        u8 pll_ipdiv;
        u8 pll_opdiv;
        u16 pll_fbdiv;
        u32 hs_clk_rate;
        unsigned int nlanes;
};

enum cdns_dphy_clk_lane_cfg {
        DPHY_CLK_CFG_LEFT_DRIVES_ALL = 0,
        DPHY_CLK_CFG_LEFT_DRIVES_RIGHT = 1,
        DPHY_CLK_CFG_LEFT_DRIVES_LEFT = 2,
        DPHY_CLK_CFG_RIGHT_DRIVES_ALL = 3,
};

struct cdns_dphy;
struct cdns_dphy_ops {
        int (*probe)(struct cdns_dphy *dphy);
        void (*remove)(struct cdns_dphy *dphy);
        void (*set_psm_div)(struct cdns_dphy *dphy, u8 div);
        void (*set_clk_lane_cfg)(struct cdns_dphy *dphy,
                                 enum cdns_dphy_clk_lane_cfg cfg);
        void (*set_pll_cfg)(struct cdns_dphy *dphy,
                            const struct cdns_dphy_cfg *cfg);
        unsigned long (*get_wakeup_time_ns)(struct cdns_dphy *dphy);
        int (*wait_for_pll_lock)(struct cdns_dphy *dphy);
        int (*wait_for_cmn_ready)(struct cdns_dphy *dphy);
};

struct cdns_dphy {
        struct cdns_dphy_cfg cfg;
        void __iomem *regs;
        struct clk *psm_clk;
        struct clk *pll_ref_clk;
        const struct cdns_dphy_ops *ops;
        struct phy *phy;
        bool is_configured;
        bool is_powered;
};

/* Order of bands is important since the index is the band number. */
static const unsigned int tx_bands[] = {
        80, 100, 120, 160, 200, 240, 320, 390, 450, 510, 560, 640, 690, 770,
        870, 950, 1000, 1200, 1400, 1600, 1800, 2000, 2200, 2500
};

static int cdns_dphy_get_pll_cfg(struct cdns_dphy *dphy,
                                 struct cdns_dphy_cfg *cfg,
                                 struct phy_configure_opts_mipi_dphy *opts)
{
        unsigned long pll_ref_hz = clk_get_rate(dphy->pll_ref_clk);
        u64 dlane_bps;

        memset(cfg, 0, sizeof(*cfg));

        if (pll_ref_hz < 9600000 || pll_ref_hz >= 150000000)
                return -EINVAL;
        else if (pll_ref_hz < 19200000)
                cfg->pll_ipdiv = 1;
        else if (pll_ref_hz < 38400000)
                cfg->pll_ipdiv = 2;
        else if (pll_ref_hz < 76800000)
                cfg->pll_ipdiv = 4;
        else
                cfg->pll_ipdiv = 8;

        dlane_bps = opts->hs_clk_rate;

        if (dlane_bps > 2500000000UL || dlane_bps < 80000000UL)
                return -EINVAL;
        else if (dlane_bps >= 1250000000)
                cfg->pll_opdiv = 1;
        else if (dlane_bps >= 630000000)
                cfg->pll_opdiv = 2;
        else if (dlane_bps >= 320000000)
                cfg->pll_opdiv = 4;
        else if (dlane_bps >= 160000000)
                cfg->pll_opdiv = 8;
        else if (dlane_bps >= 80000000)
                cfg->pll_opdiv = 16;

        cfg->pll_fbdiv = DIV_ROUND_UP_ULL(dlane_bps * 2 * cfg->pll_opdiv *
                                          cfg->pll_ipdiv,
                                          pll_ref_hz);

        cfg->hs_clk_rate = div_u64((u64)pll_ref_hz * cfg->pll_fbdiv,
                                   2 * cfg->pll_opdiv * cfg->pll_ipdiv);

        return 0;
}

static int cdns_dphy_setup_psm(struct cdns_dphy *dphy)
{
        unsigned long psm_clk_hz = clk_get_rate(dphy->psm_clk);
        unsigned long psm_div;

        if (!psm_clk_hz || psm_clk_hz > 100000000)
                return -EINVAL;

        psm_div = DIV_ROUND_CLOSEST(psm_clk_hz, 1000000);
        if (dphy->ops->set_psm_div)
                dphy->ops->set_psm_div(dphy, psm_div);

        return 0;
}

static void cdns_dphy_set_clk_lane_cfg(struct cdns_dphy *dphy,
                                       enum cdns_dphy_clk_lane_cfg cfg)
{
        if (dphy->ops->set_clk_lane_cfg)
                dphy->ops->set_clk_lane_cfg(dphy, cfg);
}

static void cdns_dphy_set_pll_cfg(struct cdns_dphy *dphy,
                                  const struct cdns_dphy_cfg *cfg)
{
        if (dphy->ops->set_pll_cfg)
                dphy->ops->set_pll_cfg(dphy, cfg);
}

static unsigned long cdns_dphy_get_wakeup_time_ns(struct cdns_dphy *dphy)
{
        return dphy->ops->get_wakeup_time_ns(dphy);
}

static int cdns_dphy_wait_for_pll_lock(struct cdns_dphy *dphy)
{
        return dphy->ops->wait_for_pll_lock ? dphy->ops->wait_for_pll_lock(dphy) : 0;
}

static int cdns_dphy_wait_for_cmn_ready(struct cdns_dphy *dphy)
{
        return  dphy->ops->wait_for_cmn_ready ? dphy->ops->wait_for_cmn_ready(dphy) : 0;
}

static unsigned long cdns_dphy_ref_get_wakeup_time_ns(struct cdns_dphy *dphy)
{
        /* Default wakeup time is 800 ns (in a simulated environment). */
        return 800;
}

static void cdns_dphy_ref_set_pll_cfg(struct cdns_dphy *dphy,
                                      const struct cdns_dphy_cfg *cfg)
{
        u32 fbdiv_low, fbdiv_high;

        fbdiv_low = (cfg->pll_fbdiv / 4) - 2;
        fbdiv_high = cfg->pll_fbdiv - fbdiv_low - 2;

        writel(DPHY_CMN_IPDIV_FROM_REG | DPHY_CMN_OPDIV_FROM_REG |
               DPHY_CMN_IPDIV(cfg->pll_ipdiv) |
               DPHY_CMN_OPDIV(cfg->pll_opdiv),
               dphy->regs + DPHY_CMN_OPIPDIV);
        writel(DPHY_CMN_FBDIV_FROM_REG |
               DPHY_CMN_FBDIV_VAL(fbdiv_low, fbdiv_high),
               dphy->regs + DPHY_CMN_FBDIV);
        writel(DPHY_CMN_PWM_HIGH(6) | DPHY_CMN_PWM_LOW(0x101) |
               DPHY_CMN_PWM_DIV(0x8),
               dphy->regs + DPHY_CMN_PWM);
}

static void cdns_dphy_ref_set_psm_div(struct cdns_dphy *dphy, u8 div)
{
        writel(DPHY_PSM_CFG_FROM_REG | DPHY_PSM_CLK_DIV(div),
               dphy->regs + DPHY_PSM_CFG);
}

static unsigned long cdns_dphy_j721e_get_wakeup_time_ns(struct cdns_dphy *dphy)
{
        /* Minimum wakeup time as per MIPI D-PHY spec v1.2 */
        return 1000000;
}

static void cdns_dphy_j721e_set_pll_cfg(struct cdns_dphy *dphy,
                                        const struct cdns_dphy_cfg *cfg)
{

        /*
         * set the PWM and PLL Byteclk divider settings to recommended values
         * which is same as that of in ref ops
         */
        writel(DPHY_CMN_PWM_HIGH(6) | DPHY_CMN_PWM_LOW(0x101) |
               DPHY_CMN_PWM_DIV(0x8),
               dphy->regs + DPHY_CMN_PWM);

        writel((FIELD_PREP(DPHY_TX_J721E_WIZ_IPDIV, cfg->pll_ipdiv) |
                FIELD_PREP(DPHY_TX_J721E_WIZ_OPDIV, cfg->pll_opdiv) |
                FIELD_PREP(DPHY_TX_J721E_WIZ_FBDIV, cfg->pll_fbdiv)),
                dphy->regs + DPHY_TX_J721E_WIZ_PLL_CTRL);

        writel(DPHY_TX_J721E_WIZ_LANE_RSTB,
               dphy->regs + DPHY_TX_J721E_WIZ_RST_CTRL);
}

static void cdns_dphy_j721e_set_psm_div(struct cdns_dphy *dphy, u8 div)
{
        writel(div, dphy->regs + DPHY_TX_J721E_WIZ_PSM_FREQ);
}

static int cdns_dphy_j721e_wait_for_pll_lock(struct cdns_dphy *dphy)
{
        u32 status;

        return readl_poll_timeout(dphy->regs + DPHY_TX_J721E_WIZ_PLL_CTRL, status,
                               status & DPHY_TX_WIZ_PLL_LOCK, 0, POLL_TIMEOUT_US);
}

static int cdns_dphy_j721e_wait_for_cmn_ready(struct cdns_dphy *dphy)
{
        u32 status;

        return readl_poll_timeout(dphy->regs + DPHY_TX_J721E_WIZ_STATUS, status,
                               status & DPHY_TX_WIZ_O_CMN_READY, 0,
                               POLL_TIMEOUT_US);
}

/*
 * This is the reference implementation of DPHY hooks. Specific integration of
 * this IP may have to re-implement some of them depending on how they decided
 * to wire things in the SoC.
 */
static const struct cdns_dphy_ops ref_dphy_ops = {
        .get_wakeup_time_ns = cdns_dphy_ref_get_wakeup_time_ns,
        .set_pll_cfg = cdns_dphy_ref_set_pll_cfg,
        .set_psm_div = cdns_dphy_ref_set_psm_div,
};

static const struct cdns_dphy_ops j721e_dphy_ops = {
        .get_wakeup_time_ns = cdns_dphy_j721e_get_wakeup_time_ns,
        .set_pll_cfg = cdns_dphy_j721e_set_pll_cfg,
        .set_psm_div = cdns_dphy_j721e_set_psm_div,
        .wait_for_pll_lock = cdns_dphy_j721e_wait_for_pll_lock,
        .wait_for_cmn_ready = cdns_dphy_j721e_wait_for_cmn_ready,
};

static int cdns_dphy_config_from_opts(struct phy *phy,
                                      struct phy_configure_opts_mipi_dphy *opts,
                                      struct cdns_dphy_cfg *cfg)
{
        struct cdns_dphy *dphy = phy_get_drvdata(phy);
        int ret;

        ret = phy_mipi_dphy_config_validate(opts);
        if (ret)
                return ret;

        ret = cdns_dphy_get_pll_cfg(dphy, cfg, opts);
        if (ret)
                return ret;

        opts->hs_clk_rate = cfg->hs_clk_rate;
        opts->wakeup = cdns_dphy_get_wakeup_time_ns(dphy) / 1000;

        return 0;
}

static int cdns_dphy_tx_get_band_ctrl(unsigned long hs_clk_rate)
{
        unsigned int rate;
        int i;

        rate = hs_clk_rate / 1000000UL;

        if (rate < tx_bands[0])
                return -EOPNOTSUPP;

        for (i = 0; i < ARRAY_SIZE(tx_bands) - 1; i++) {
                if (rate >= tx_bands[i] && rate < tx_bands[i + 1])
                        return i;
        }

        return -EOPNOTSUPP;
}

static int cdns_dphy_validate(struct phy *phy, enum phy_mode mode, int submode,
                              union phy_configure_opts *opts)
{
        struct cdns_dphy_cfg cfg = { 0 };

        if (mode != PHY_MODE_MIPI_DPHY)
                return -EINVAL;

        return cdns_dphy_config_from_opts(phy, &opts->mipi_dphy, &cfg);
}

static int cdns_dphy_configure(struct phy *phy, union phy_configure_opts *opts)
{
        struct cdns_dphy *dphy = phy_get_drvdata(phy);
        int ret;

        ret = cdns_dphy_config_from_opts(phy, &opts->mipi_dphy, &dphy->cfg);
        if (!ret)
                dphy->is_configured = true;

        return ret;
}

static int cdns_dphy_power_on(struct phy *phy)
{
        struct cdns_dphy *dphy = phy_get_drvdata(phy);
        int ret;
        u32 reg;

        if (!dphy->is_configured || dphy->is_powered)
                return -EINVAL;

        clk_prepare_enable(dphy->psm_clk);
        clk_prepare_enable(dphy->pll_ref_clk);

        /*
         * Configure the internal PSM clk divider so that the DPHY has a
         * 1MHz clk (or something close).
         */
        ret = cdns_dphy_setup_psm(dphy);
        if (ret) {
                dev_err(&dphy->phy->dev, "Failed to setup PSM with error %d\n", ret);
                goto err_power_on;
        }

        /*
         * Configure attach clk lanes to data lanes: the DPHY has 2 clk lanes
         * and 8 data lanes, each clk lane can be attache different set of
         * data lanes. The 2 groups are named 'left' and 'right', so here we
         * just say that we want the 'left' clk lane to drive the 'left' data
         * lanes.
         */
        cdns_dphy_set_clk_lane_cfg(dphy, DPHY_CLK_CFG_LEFT_DRIVES_LEFT);

        /*
         * Configure the DPHY PLL that will be used to generate the TX byte
         * clk.
         */
        cdns_dphy_set_pll_cfg(dphy, &dphy->cfg);

        ret = cdns_dphy_tx_get_band_ctrl(dphy->cfg.hs_clk_rate);
        if (ret < 0) {
                dev_err(&dphy->phy->dev, "Failed to get band control value with error %d\n", ret);
                goto err_power_on;
        }

        reg = FIELD_PREP(DPHY_BAND_CFG_LEFT_BAND, ret) |
              FIELD_PREP(DPHY_BAND_CFG_RIGHT_BAND, ret);
        writel(reg, dphy->regs + DPHY_BAND_CFG);

        /* Start TX state machine. */
        reg = readl(dphy->regs + DPHY_CMN_SSM);
        writel((reg & DPHY_CMN_SSM_CAL_WAIT_TIME) | DPHY_CMN_SSM_EN | DPHY_CMN_TX_MODE_EN,
               dphy->regs + DPHY_CMN_SSM);

        ret = cdns_dphy_wait_for_pll_lock(dphy);
        if (ret) {
                dev_err(&dphy->phy->dev, "Failed to lock PLL with error %d\n", ret);
                goto err_power_on;
        }

        ret = cdns_dphy_wait_for_cmn_ready(dphy);
        if (ret) {
                dev_err(&dphy->phy->dev, "O_CMN_READY signal failed to assert with error %d\n",
                        ret);
                goto err_power_on;
        }

        dphy->is_powered = true;

        return 0;

err_power_on:
        clk_disable_unprepare(dphy->pll_ref_clk);
        clk_disable_unprepare(dphy->psm_clk);

        return ret;
}

static int cdns_dphy_power_off(struct phy *phy)
{
        struct cdns_dphy *dphy = phy_get_drvdata(phy);
        u32 reg;

        clk_disable_unprepare(dphy->pll_ref_clk);
        clk_disable_unprepare(dphy->psm_clk);

        /* Stop TX state machine. */
        reg = readl(dphy->regs + DPHY_CMN_SSM);
        writel(reg & ~DPHY_CMN_SSM_EN, dphy->regs + DPHY_CMN_SSM);

        dphy->is_powered = false;

        return 0;
}

static const struct phy_ops cdns_dphy_ops = {
        .configure      = cdns_dphy_configure,
        .validate       = cdns_dphy_validate,
        .power_on       = cdns_dphy_power_on,
        .power_off      = cdns_dphy_power_off,
};

static int cdns_dphy_probe(struct platform_device *pdev)
{
        struct phy_provider *phy_provider;
        struct cdns_dphy *dphy;
        int ret;

        dphy = devm_kzalloc(&pdev->dev, sizeof(*dphy), GFP_KERNEL);
        if (!dphy)
                return -ENOMEM;
        dev_set_drvdata(&pdev->dev, dphy);

        dphy->ops = of_device_get_match_data(&pdev->dev);
        if (!dphy->ops)
                return -EINVAL;

        dphy->regs = devm_platform_ioremap_resource(pdev, 0);
        if (IS_ERR(dphy->regs))
                return PTR_ERR(dphy->regs);

        dphy->psm_clk = devm_clk_get(&pdev->dev, "psm");
        if (IS_ERR(dphy->psm_clk))
                return PTR_ERR(dphy->psm_clk);

        dphy->pll_ref_clk = devm_clk_get(&pdev->dev, "pll_ref");
        if (IS_ERR(dphy->pll_ref_clk))
                return PTR_ERR(dphy->pll_ref_clk);

        if (dphy->ops->probe) {
                ret = dphy->ops->probe(dphy);
                if (ret)
                        return ret;
        }

        dphy->phy = devm_phy_create(&pdev->dev, NULL, &cdns_dphy_ops);
        if (IS_ERR(dphy->phy)) {
                dev_err(&pdev->dev, "failed to create PHY\n");
                if (dphy->ops->remove)
                        dphy->ops->remove(dphy);
                return PTR_ERR(dphy->phy);
        }

        phy_set_drvdata(dphy->phy, dphy);
        phy_provider = devm_of_phy_provider_register(&pdev->dev,
                                                     of_phy_simple_xlate);

        return PTR_ERR_OR_ZERO(phy_provider);
}

static void cdns_dphy_remove(struct platform_device *pdev)
{
        struct cdns_dphy *dphy = dev_get_drvdata(&pdev->dev);

        if (dphy->ops->remove)
                dphy->ops->remove(dphy);
}

static const struct of_device_id cdns_dphy_of_match[] = {
        { .compatible = "cdns,dphy", .data = &ref_dphy_ops },
        { .compatible = "ti,j721e-dphy", .data = &j721e_dphy_ops },
        { /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, cdns_dphy_of_match);

static struct platform_driver cdns_dphy_platform_driver = {
        .probe          = cdns_dphy_probe,
        .remove         = cdns_dphy_remove,
        .driver         = {
                .name           = "cdns-mipi-dphy",
                .of_match_table = cdns_dphy_of_match,
        },
};
module_platform_driver(cdns_dphy_platform_driver);

MODULE_AUTHOR("Maxime Ripard <maxime.ripard@bootlin.com>");
MODULE_DESCRIPTION("Cadence MIPI D-PHY Driver");
MODULE_LICENSE("GPL");