linux/drivers/phy/cadence/cdns-dphy.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * Copyright: 2017-2018 Cadence Design Systems, Inc.
   4 */
   5
   6#include <linux/bitops.h>
   7#include <linux/clk.h>
   8#include <linux/io.h>
   9#include <linux/module.h>
  10#include <linux/of_address.h>
  11#include <linux/of_device.h>
  12#include <linux/platform_device.h>
  13#include <linux/reset.h>
  14
  15#include <linux/phy/phy.h>
  16#include <linux/phy/phy-mipi-dphy.h>
  17
  18#define REG_WAKEUP_TIME_NS              800
  19#define DPHY_PLL_RATE_HZ                108000000
  20
  21/* DPHY registers */
  22#define DPHY_PMA_CMN(reg)               (reg)
  23#define DPHY_PMA_LCLK(reg)              (0x100 + (reg))
  24#define DPHY_PMA_LDATA(lane, reg)       (0x200 + ((lane) * 0x100) + (reg))
  25#define DPHY_PMA_RCLK(reg)              (0x600 + (reg))
  26#define DPHY_PMA_RDATA(lane, reg)       (0x700 + ((lane) * 0x100) + (reg))
  27#define DPHY_PCS(reg)                   (0xb00 + (reg))
  28
  29#define DPHY_CMN_SSM                    DPHY_PMA_CMN(0x20)
  30#define DPHY_CMN_SSM_EN                 BIT(0)
  31#define DPHY_CMN_TX_MODE_EN             BIT(9)
  32
  33#define DPHY_CMN_PWM                    DPHY_PMA_CMN(0x40)
  34#define DPHY_CMN_PWM_DIV(x)             ((x) << 20)
  35#define DPHY_CMN_PWM_LOW(x)             ((x) << 10)
  36#define DPHY_CMN_PWM_HIGH(x)            (x)
  37
  38#define DPHY_CMN_FBDIV                  DPHY_PMA_CMN(0x4c)
  39#define DPHY_CMN_FBDIV_VAL(low, high)   (((high) << 11) | ((low) << 22))
  40#define DPHY_CMN_FBDIV_FROM_REG         (BIT(10) | BIT(21))
  41
  42#define DPHY_CMN_OPIPDIV                DPHY_PMA_CMN(0x50)
  43#define DPHY_CMN_IPDIV_FROM_REG         BIT(0)
  44#define DPHY_CMN_IPDIV(x)               ((x) << 1)
  45#define DPHY_CMN_OPDIV_FROM_REG         BIT(6)
  46#define DPHY_CMN_OPDIV(x)               ((x) << 7)
  47
  48#define DPHY_PSM_CFG                    DPHY_PCS(0x4)
  49#define DPHY_PSM_CFG_FROM_REG           BIT(0)
  50#define DPHY_PSM_CLK_DIV(x)             ((x) << 1)
  51
  52#define DSI_HBP_FRAME_OVERHEAD          12
  53#define DSI_HSA_FRAME_OVERHEAD          14
  54#define DSI_HFP_FRAME_OVERHEAD          6
  55#define DSI_HSS_VSS_VSE_FRAME_OVERHEAD  4
  56#define DSI_BLANKING_FRAME_OVERHEAD     6
  57#define DSI_NULL_FRAME_OVERHEAD         6
  58#define DSI_EOT_PKT_SIZE                4
  59
  60struct cdns_dphy_cfg {
  61        u8 pll_ipdiv;
  62        u8 pll_opdiv;
  63        u16 pll_fbdiv;
  64        unsigned int nlanes;
  65};
  66
  67enum cdns_dphy_clk_lane_cfg {
  68        DPHY_CLK_CFG_LEFT_DRIVES_ALL = 0,
  69        DPHY_CLK_CFG_LEFT_DRIVES_RIGHT = 1,
  70        DPHY_CLK_CFG_LEFT_DRIVES_LEFT = 2,
  71        DPHY_CLK_CFG_RIGHT_DRIVES_ALL = 3,
  72};
  73
  74struct cdns_dphy;
  75struct cdns_dphy_ops {
  76        int (*probe)(struct cdns_dphy *dphy);
  77        void (*remove)(struct cdns_dphy *dphy);
  78        void (*set_psm_div)(struct cdns_dphy *dphy, u8 div);
  79        void (*set_clk_lane_cfg)(struct cdns_dphy *dphy,
  80                                 enum cdns_dphy_clk_lane_cfg cfg);
  81        void (*set_pll_cfg)(struct cdns_dphy *dphy,
  82                            const struct cdns_dphy_cfg *cfg);
  83        unsigned long (*get_wakeup_time_ns)(struct cdns_dphy *dphy);
  84};
  85
  86struct cdns_dphy {
  87        struct cdns_dphy_cfg cfg;
  88        void __iomem *regs;
  89        struct clk *psm_clk;
  90        struct clk *pll_ref_clk;
  91        const struct cdns_dphy_ops *ops;
  92        struct phy *phy;
  93};
  94
  95static int cdns_dsi_get_dphy_pll_cfg(struct cdns_dphy *dphy,
  96                                     struct cdns_dphy_cfg *cfg,
  97                                     struct phy_configure_opts_mipi_dphy *opts,
  98                                     unsigned int *dsi_hfp_ext)
  99{
 100        unsigned long pll_ref_hz = clk_get_rate(dphy->pll_ref_clk);
 101        u64 dlane_bps;
 102
 103        memset(cfg, 0, sizeof(*cfg));
 104
 105        if (pll_ref_hz < 9600000 || pll_ref_hz >= 150000000)
 106                return -EINVAL;
 107        else if (pll_ref_hz < 19200000)
 108                cfg->pll_ipdiv = 1;
 109        else if (pll_ref_hz < 38400000)
 110                cfg->pll_ipdiv = 2;
 111        else if (pll_ref_hz < 76800000)
 112                cfg->pll_ipdiv = 4;
 113        else
 114                cfg->pll_ipdiv = 8;
 115
 116        dlane_bps = opts->hs_clk_rate;
 117
 118        if (dlane_bps > 2500000000UL || dlane_bps < 160000000UL)
 119                return -EINVAL;
 120        else if (dlane_bps >= 1250000000)
 121                cfg->pll_opdiv = 1;
 122        else if (dlane_bps >= 630000000)
 123                cfg->pll_opdiv = 2;
 124        else if (dlane_bps >= 320000000)
 125                cfg->pll_opdiv = 4;
 126        else if (dlane_bps >= 160000000)
 127                cfg->pll_opdiv = 8;
 128
 129        cfg->pll_fbdiv = DIV_ROUND_UP_ULL(dlane_bps * 2 * cfg->pll_opdiv *
 130                                          cfg->pll_ipdiv,
 131                                          pll_ref_hz);
 132
 133        return 0;
 134}
 135
 136static int cdns_dphy_setup_psm(struct cdns_dphy *dphy)
 137{
 138        unsigned long psm_clk_hz = clk_get_rate(dphy->psm_clk);
 139        unsigned long psm_div;
 140
 141        if (!psm_clk_hz || psm_clk_hz > 100000000)
 142                return -EINVAL;
 143
 144        psm_div = DIV_ROUND_CLOSEST(psm_clk_hz, 1000000);
 145        if (dphy->ops->set_psm_div)
 146                dphy->ops->set_psm_div(dphy, psm_div);
 147
 148        return 0;
 149}
 150
 151static void cdns_dphy_set_clk_lane_cfg(struct cdns_dphy *dphy,
 152                                       enum cdns_dphy_clk_lane_cfg cfg)
 153{
 154        if (dphy->ops->set_clk_lane_cfg)
 155                dphy->ops->set_clk_lane_cfg(dphy, cfg);
 156}
 157
 158static void cdns_dphy_set_pll_cfg(struct cdns_dphy *dphy,
 159                                  const struct cdns_dphy_cfg *cfg)
 160{
 161        if (dphy->ops->set_pll_cfg)
 162                dphy->ops->set_pll_cfg(dphy, cfg);
 163}
 164
 165static unsigned long cdns_dphy_get_wakeup_time_ns(struct cdns_dphy *dphy)
 166{
 167        return dphy->ops->get_wakeup_time_ns(dphy);
 168}
 169
 170static unsigned long cdns_dphy_ref_get_wakeup_time_ns(struct cdns_dphy *dphy)
 171{
 172        /* Default wakeup time is 800 ns (in a simulated environment). */
 173        return 800;
 174}
 175
 176static void cdns_dphy_ref_set_pll_cfg(struct cdns_dphy *dphy,
 177                                      const struct cdns_dphy_cfg *cfg)
 178{
 179        u32 fbdiv_low, fbdiv_high;
 180
 181        fbdiv_low = (cfg->pll_fbdiv / 4) - 2;
 182        fbdiv_high = cfg->pll_fbdiv - fbdiv_low - 2;
 183
 184        writel(DPHY_CMN_IPDIV_FROM_REG | DPHY_CMN_OPDIV_FROM_REG |
 185               DPHY_CMN_IPDIV(cfg->pll_ipdiv) |
 186               DPHY_CMN_OPDIV(cfg->pll_opdiv),
 187               dphy->regs + DPHY_CMN_OPIPDIV);
 188        writel(DPHY_CMN_FBDIV_FROM_REG |
 189               DPHY_CMN_FBDIV_VAL(fbdiv_low, fbdiv_high),
 190               dphy->regs + DPHY_CMN_FBDIV);
 191        writel(DPHY_CMN_PWM_HIGH(6) | DPHY_CMN_PWM_LOW(0x101) |
 192               DPHY_CMN_PWM_DIV(0x8),
 193               dphy->regs + DPHY_CMN_PWM);
 194}
 195
 196static void cdns_dphy_ref_set_psm_div(struct cdns_dphy *dphy, u8 div)
 197{
 198        writel(DPHY_PSM_CFG_FROM_REG | DPHY_PSM_CLK_DIV(div),
 199               dphy->regs + DPHY_PSM_CFG);
 200}
 201
 202/*
 203 * This is the reference implementation of DPHY hooks. Specific integration of
 204 * this IP may have to re-implement some of them depending on how they decided
 205 * to wire things in the SoC.
 206 */
 207static const struct cdns_dphy_ops ref_dphy_ops = {
 208        .get_wakeup_time_ns = cdns_dphy_ref_get_wakeup_time_ns,
 209        .set_pll_cfg = cdns_dphy_ref_set_pll_cfg,
 210        .set_psm_div = cdns_dphy_ref_set_psm_div,
 211};
 212
 213static int cdns_dphy_config_from_opts(struct phy *phy,
 214                                      struct phy_configure_opts_mipi_dphy *opts,
 215                                      struct cdns_dphy_cfg *cfg)
 216{
 217        struct cdns_dphy *dphy = phy_get_drvdata(phy);
 218        unsigned int dsi_hfp_ext = 0;
 219        int ret;
 220
 221        ret = phy_mipi_dphy_config_validate(opts);
 222        if (ret)
 223                return ret;
 224
 225        ret = cdns_dsi_get_dphy_pll_cfg(dphy, cfg,
 226                                        opts, &dsi_hfp_ext);
 227        if (ret)
 228                return ret;
 229
 230        opts->wakeup = cdns_dphy_get_wakeup_time_ns(dphy) / 1000;
 231
 232        return 0;
 233}
 234
 235static int cdns_dphy_validate(struct phy *phy, enum phy_mode mode, int submode,
 236                              union phy_configure_opts *opts)
 237{
 238        struct cdns_dphy_cfg cfg = { 0 };
 239
 240        if (mode != PHY_MODE_MIPI_DPHY)
 241                return -EINVAL;
 242
 243        return cdns_dphy_config_from_opts(phy, &opts->mipi_dphy, &cfg);
 244}
 245
 246static int cdns_dphy_configure(struct phy *phy, union phy_configure_opts *opts)
 247{
 248        struct cdns_dphy *dphy = phy_get_drvdata(phy);
 249        struct cdns_dphy_cfg cfg = { 0 };
 250        int ret;
 251
 252        ret = cdns_dphy_config_from_opts(phy, &opts->mipi_dphy, &cfg);
 253        if (ret)
 254                return ret;
 255
 256        /*
 257         * Configure the internal PSM clk divider so that the DPHY has a
 258         * 1MHz clk (or something close).
 259         */
 260        ret = cdns_dphy_setup_psm(dphy);
 261        if (ret)
 262                return ret;
 263
 264        /*
 265         * Configure attach clk lanes to data lanes: the DPHY has 2 clk lanes
 266         * and 8 data lanes, each clk lane can be attache different set of
 267         * data lanes. The 2 groups are named 'left' and 'right', so here we
 268         * just say that we want the 'left' clk lane to drive the 'left' data
 269         * lanes.
 270         */
 271        cdns_dphy_set_clk_lane_cfg(dphy, DPHY_CLK_CFG_LEFT_DRIVES_LEFT);
 272
 273        /*
 274         * Configure the DPHY PLL that will be used to generate the TX byte
 275         * clk.
 276         */
 277        cdns_dphy_set_pll_cfg(dphy, &cfg);
 278
 279        return 0;
 280}
 281
 282static int cdns_dphy_power_on(struct phy *phy)
 283{
 284        struct cdns_dphy *dphy = phy_get_drvdata(phy);
 285
 286        clk_prepare_enable(dphy->psm_clk);
 287        clk_prepare_enable(dphy->pll_ref_clk);
 288
 289        /* Start TX state machine. */
 290        writel(DPHY_CMN_SSM_EN | DPHY_CMN_TX_MODE_EN,
 291               dphy->regs + DPHY_CMN_SSM);
 292
 293        return 0;
 294}
 295
 296static int cdns_dphy_power_off(struct phy *phy)
 297{
 298        struct cdns_dphy *dphy = phy_get_drvdata(phy);
 299
 300        clk_disable_unprepare(dphy->pll_ref_clk);
 301        clk_disable_unprepare(dphy->psm_clk);
 302
 303        return 0;
 304}
 305
 306static const struct phy_ops cdns_dphy_ops = {
 307        .configure      = cdns_dphy_configure,
 308        .validate       = cdns_dphy_validate,
 309        .power_on       = cdns_dphy_power_on,
 310        .power_off      = cdns_dphy_power_off,
 311};
 312
 313static int cdns_dphy_probe(struct platform_device *pdev)
 314{
 315        struct phy_provider *phy_provider;
 316        struct cdns_dphy *dphy;
 317        struct resource *res;
 318        int ret;
 319
 320        dphy = devm_kzalloc(&pdev->dev, sizeof(*dphy), GFP_KERNEL);
 321        if (!dphy)
 322                return -ENOMEM;
 323        dev_set_drvdata(&pdev->dev, dphy);
 324
 325        dphy->ops = of_device_get_match_data(&pdev->dev);
 326        if (!dphy->ops)
 327                return -EINVAL;
 328
 329        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 330        dphy->regs = devm_ioremap_resource(&pdev->dev, res);
 331        if (IS_ERR(dphy->regs))
 332                return PTR_ERR(dphy->regs);
 333
 334        dphy->psm_clk = devm_clk_get(&pdev->dev, "psm");
 335        if (IS_ERR(dphy->psm_clk))
 336                return PTR_ERR(dphy->psm_clk);
 337
 338        dphy->pll_ref_clk = devm_clk_get(&pdev->dev, "pll_ref");
 339        if (IS_ERR(dphy->pll_ref_clk))
 340                return PTR_ERR(dphy->pll_ref_clk);
 341
 342        if (dphy->ops->probe) {
 343                ret = dphy->ops->probe(dphy);
 344                if (ret)
 345                        return ret;
 346        }
 347
 348        dphy->phy = devm_phy_create(&pdev->dev, NULL, &cdns_dphy_ops);
 349        if (IS_ERR(dphy->phy)) {
 350                dev_err(&pdev->dev, "failed to create PHY\n");
 351                if (dphy->ops->remove)
 352                        dphy->ops->remove(dphy);
 353                return PTR_ERR(dphy->phy);
 354        }
 355
 356        phy_set_drvdata(dphy->phy, dphy);
 357        phy_provider = devm_of_phy_provider_register(&pdev->dev,
 358                                                     of_phy_simple_xlate);
 359
 360        return PTR_ERR_OR_ZERO(phy_provider);
 361}
 362
 363static int cdns_dphy_remove(struct platform_device *pdev)
 364{
 365        struct cdns_dphy *dphy = dev_get_drvdata(&pdev->dev);
 366
 367        if (dphy->ops->remove)
 368                dphy->ops->remove(dphy);
 369
 370        return 0;
 371}
 372
 373static const struct of_device_id cdns_dphy_of_match[] = {
 374        { .compatible = "cdns,dphy", .data = &ref_dphy_ops },
 375        { /* sentinel */ },
 376};
 377MODULE_DEVICE_TABLE(of, cdns_dphy_of_match);
 378
 379static struct platform_driver cdns_dphy_platform_driver = {
 380        .probe          = cdns_dphy_probe,
 381        .remove         = cdns_dphy_remove,
 382        .driver         = {
 383                .name           = "cdns-mipi-dphy",
 384                .of_match_table = cdns_dphy_of_match,
 385        },
 386};
 387module_platform_driver(cdns_dphy_platform_driver);
 388
 389MODULE_AUTHOR("Maxime Ripard <maxime.ripard@bootlin.com>");
 390MODULE_DESCRIPTION("Cadence MIPI D-PHY Driver");
 391MODULE_LICENSE("GPL");
 392