linux/drivers/scsi/ufs/cdns-pltfrm.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * Platform UFS Host driver for Cadence controller
   4 *
   5 * Copyright (C) 2018 Cadence Design Systems, Inc.
   6 *
   7 * Authors:
   8 *      Jan Kotas <jank@cadence.com>
   9 *
  10 */
  11
  12#include <linux/kernel.h>
  13#include <linux/module.h>
  14#include <linux/platform_device.h>
  15#include <linux/of.h>
  16#include <linux/time.h>
  17
  18#include "ufshcd-pltfrm.h"
  19
  20#define CDNS_UFS_REG_HCLKDIV    0xFC
  21#define CDNS_UFS_REG_PHY_XCFGD1 0x113C
  22#define CDNS_UFS_MAX_L4_ATTRS 12
  23
  24struct cdns_ufs_host {
  25        /**
  26         * cdns_ufs_dme_attr_val - for storing L4 attributes
  27         */
  28        u32 cdns_ufs_dme_attr_val[CDNS_UFS_MAX_L4_ATTRS];
  29};
  30
  31/**
  32 * cdns_ufs_get_l4_attr - get L4 attributes on local side
  33 * @hba: per adapter instance
  34 *
  35 */
  36static void cdns_ufs_get_l4_attr(struct ufs_hba *hba)
  37{
  38        struct cdns_ufs_host *host = ufshcd_get_variant(hba);
  39
  40        ufshcd_dme_get(hba, UIC_ARG_MIB(T_PEERDEVICEID),
  41                       &host->cdns_ufs_dme_attr_val[0]);
  42        ufshcd_dme_get(hba, UIC_ARG_MIB(T_PEERCPORTID),
  43                       &host->cdns_ufs_dme_attr_val[1]);
  44        ufshcd_dme_get(hba, UIC_ARG_MIB(T_TRAFFICCLASS),
  45                       &host->cdns_ufs_dme_attr_val[2]);
  46        ufshcd_dme_get(hba, UIC_ARG_MIB(T_PROTOCOLID),
  47                       &host->cdns_ufs_dme_attr_val[3]);
  48        ufshcd_dme_get(hba, UIC_ARG_MIB(T_CPORTFLAGS),
  49                       &host->cdns_ufs_dme_attr_val[4]);
  50        ufshcd_dme_get(hba, UIC_ARG_MIB(T_TXTOKENVALUE),
  51                       &host->cdns_ufs_dme_attr_val[5]);
  52        ufshcd_dme_get(hba, UIC_ARG_MIB(T_RXTOKENVALUE),
  53                       &host->cdns_ufs_dme_attr_val[6]);
  54        ufshcd_dme_get(hba, UIC_ARG_MIB(T_LOCALBUFFERSPACE),
  55                       &host->cdns_ufs_dme_attr_val[7]);
  56        ufshcd_dme_get(hba, UIC_ARG_MIB(T_PEERBUFFERSPACE),
  57                       &host->cdns_ufs_dme_attr_val[8]);
  58        ufshcd_dme_get(hba, UIC_ARG_MIB(T_CREDITSTOSEND),
  59                       &host->cdns_ufs_dme_attr_val[9]);
  60        ufshcd_dme_get(hba, UIC_ARG_MIB(T_CPORTMODE),
  61                       &host->cdns_ufs_dme_attr_val[10]);
  62        ufshcd_dme_get(hba, UIC_ARG_MIB(T_CONNECTIONSTATE),
  63                       &host->cdns_ufs_dme_attr_val[11]);
  64}
  65
  66/**
  67 * cdns_ufs_set_l4_attr - set L4 attributes on local side
  68 * @hba: per adapter instance
  69 *
  70 */
  71static void cdns_ufs_set_l4_attr(struct ufs_hba *hba)
  72{
  73        struct cdns_ufs_host *host = ufshcd_get_variant(hba);
  74
  75        ufshcd_dme_set(hba, UIC_ARG_MIB(T_CONNECTIONSTATE), 0);
  76        ufshcd_dme_set(hba, UIC_ARG_MIB(T_PEERDEVICEID),
  77                       host->cdns_ufs_dme_attr_val[0]);
  78        ufshcd_dme_set(hba, UIC_ARG_MIB(T_PEERCPORTID),
  79                       host->cdns_ufs_dme_attr_val[1]);
  80        ufshcd_dme_set(hba, UIC_ARG_MIB(T_TRAFFICCLASS),
  81                       host->cdns_ufs_dme_attr_val[2]);
  82        ufshcd_dme_set(hba, UIC_ARG_MIB(T_PROTOCOLID),
  83                       host->cdns_ufs_dme_attr_val[3]);
  84        ufshcd_dme_set(hba, UIC_ARG_MIB(T_CPORTFLAGS),
  85                       host->cdns_ufs_dme_attr_val[4]);
  86        ufshcd_dme_set(hba, UIC_ARG_MIB(T_TXTOKENVALUE),
  87                       host->cdns_ufs_dme_attr_val[5]);
  88        ufshcd_dme_set(hba, UIC_ARG_MIB(T_RXTOKENVALUE),
  89                       host->cdns_ufs_dme_attr_val[6]);
  90        ufshcd_dme_set(hba, UIC_ARG_MIB(T_LOCALBUFFERSPACE),
  91                       host->cdns_ufs_dme_attr_val[7]);
  92        ufshcd_dme_set(hba, UIC_ARG_MIB(T_PEERBUFFERSPACE),
  93                       host->cdns_ufs_dme_attr_val[8]);
  94        ufshcd_dme_set(hba, UIC_ARG_MIB(T_CREDITSTOSEND),
  95                       host->cdns_ufs_dme_attr_val[9]);
  96        ufshcd_dme_set(hba, UIC_ARG_MIB(T_CPORTMODE),
  97                       host->cdns_ufs_dme_attr_val[10]);
  98        ufshcd_dme_set(hba, UIC_ARG_MIB(T_CONNECTIONSTATE),
  99                       host->cdns_ufs_dme_attr_val[11]);
 100}
 101
 102/**
 103 * cdns_ufs_set_hclkdiv()
 104 * Sets HCLKDIV register value based on the core_clk
 105 * @hba: host controller instance
 106 *
 107 * Return zero for success and non-zero for failure
 108 */
 109static int cdns_ufs_set_hclkdiv(struct ufs_hba *hba)
 110{
 111        struct ufs_clk_info *clki;
 112        struct list_head *head = &hba->clk_list_head;
 113        unsigned long core_clk_rate = 0;
 114        u32 core_clk_div = 0;
 115
 116        if (list_empty(head))
 117                return 0;
 118
 119        list_for_each_entry(clki, head, list) {
 120                if (IS_ERR_OR_NULL(clki->clk))
 121                        continue;
 122                if (!strcmp(clki->name, "core_clk"))
 123                        core_clk_rate = clk_get_rate(clki->clk);
 124        }
 125
 126        if (!core_clk_rate) {
 127                dev_err(hba->dev, "%s: unable to find core_clk rate\n",
 128                        __func__);
 129                return -EINVAL;
 130        }
 131
 132        core_clk_div = core_clk_rate / USEC_PER_SEC;
 133
 134        ufshcd_writel(hba, core_clk_div, CDNS_UFS_REG_HCLKDIV);
 135        /**
 136         * Make sure the register was updated,
 137         * UniPro layer will not work with an incorrect value.
 138         */
 139        mb();
 140
 141        return 0;
 142}
 143
 144/**
 145 * cdns_ufs_hce_enable_notify()
 146 * Called before and after HCE enable bit is set.
 147 * @hba: host controller instance
 148 * @status: notify stage (pre, post change)
 149 *
 150 * Return zero for success and non-zero for failure
 151 */
 152static int cdns_ufs_hce_enable_notify(struct ufs_hba *hba,
 153                                      enum ufs_notify_change_status status)
 154{
 155        if (status != PRE_CHANGE)
 156                return 0;
 157
 158        return cdns_ufs_set_hclkdiv(hba);
 159}
 160
 161/**
 162 * cdns_ufs_hibern8_notify()
 163 * Called around hibern8 enter/exit.
 164 * @hba: host controller instance
 165 * @cmd: UIC Command
 166 * @status: notify stage (pre, post change)
 167 *
 168 */
 169static void cdns_ufs_hibern8_notify(struct ufs_hba *hba, enum uic_cmd_dme cmd,
 170                                    enum ufs_notify_change_status status)
 171{
 172        if (status == PRE_CHANGE && cmd == UIC_CMD_DME_HIBER_ENTER)
 173                cdns_ufs_get_l4_attr(hba);
 174        if (status == POST_CHANGE && cmd == UIC_CMD_DME_HIBER_EXIT)
 175                cdns_ufs_set_l4_attr(hba);
 176}
 177
 178/**
 179 * cdns_ufs_link_startup_notify()
 180 * Called before and after Link startup is carried out.
 181 * @hba: host controller instance
 182 * @status: notify stage (pre, post change)
 183 *
 184 * Return zero for success and non-zero for failure
 185 */
 186static int cdns_ufs_link_startup_notify(struct ufs_hba *hba,
 187                                        enum ufs_notify_change_status status)
 188{
 189        if (status != PRE_CHANGE)
 190                return 0;
 191
 192        /*
 193         * Some UFS devices have issues if LCC is enabled.
 194         * So we are setting PA_Local_TX_LCC_Enable to 0
 195         * before link startup which will make sure that both host
 196         * and device TX LCC are disabled once link startup is
 197         * completed.
 198         */
 199        ufshcd_disable_host_tx_lcc(hba);
 200
 201        /*
 202         * Disabling Autohibern8 feature in cadence UFS
 203         * to mask unexpected interrupt trigger.
 204         */
 205        hba->ahit = 0;
 206
 207        return 0;
 208}
 209
 210/**
 211 * cdns_ufs_init - performs additional ufs initialization
 212 * @hba: host controller instance
 213 *
 214 * Returns status of initialization
 215 */
 216static int cdns_ufs_init(struct ufs_hba *hba)
 217{
 218        int status = 0;
 219        struct cdns_ufs_host *host;
 220        struct device *dev = hba->dev;
 221
 222        host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL);
 223
 224        if (!host)
 225                return -ENOMEM;
 226        ufshcd_set_variant(hba, host);
 227
 228        status = ufshcd_vops_phy_initialization(hba);
 229
 230        return status;
 231}
 232
 233/**
 234 * cdns_ufs_m31_16nm_phy_initialization - performs m31 phy initialization
 235 * @hba: host controller instance
 236 *
 237 * Always returns 0
 238 */
 239static int cdns_ufs_m31_16nm_phy_initialization(struct ufs_hba *hba)
 240{
 241        u32 data;
 242
 243        /* Increase RX_Advanced_Min_ActivateTime_Capability */
 244        data = ufshcd_readl(hba, CDNS_UFS_REG_PHY_XCFGD1);
 245        data |= BIT(24);
 246        ufshcd_writel(hba, data, CDNS_UFS_REG_PHY_XCFGD1);
 247
 248        return 0;
 249}
 250
 251static const struct ufs_hba_variant_ops cdns_ufs_pltfm_hba_vops = {
 252        .name = "cdns-ufs-pltfm",
 253        .init = cdns_ufs_init,
 254        .hce_enable_notify = cdns_ufs_hce_enable_notify,
 255        .link_startup_notify = cdns_ufs_link_startup_notify,
 256        .hibern8_notify = cdns_ufs_hibern8_notify,
 257};
 258
 259static const struct ufs_hba_variant_ops cdns_ufs_m31_16nm_pltfm_hba_vops = {
 260        .name = "cdns-ufs-pltfm",
 261        .init = cdns_ufs_init,
 262        .hce_enable_notify = cdns_ufs_hce_enable_notify,
 263        .link_startup_notify = cdns_ufs_link_startup_notify,
 264        .phy_initialization = cdns_ufs_m31_16nm_phy_initialization,
 265        .hibern8_notify = cdns_ufs_hibern8_notify,
 266};
 267
 268static const struct of_device_id cdns_ufs_of_match[] = {
 269        {
 270                .compatible = "cdns,ufshc",
 271                .data =  &cdns_ufs_pltfm_hba_vops,
 272        },
 273        {
 274                .compatible = "cdns,ufshc-m31-16nm",
 275                .data =  &cdns_ufs_m31_16nm_pltfm_hba_vops,
 276        },
 277        { },
 278};
 279
 280MODULE_DEVICE_TABLE(of, cdns_ufs_of_match);
 281
 282/**
 283 * cdns_ufs_pltfrm_probe - probe routine of the driver
 284 * @pdev: pointer to platform device handle
 285 *
 286 * Return zero for success and non-zero for failure
 287 */
 288static int cdns_ufs_pltfrm_probe(struct platform_device *pdev)
 289{
 290        int err;
 291        const struct of_device_id *of_id;
 292        struct ufs_hba_variant_ops *vops;
 293        struct device *dev = &pdev->dev;
 294
 295        of_id = of_match_node(cdns_ufs_of_match, dev->of_node);
 296        vops = (struct ufs_hba_variant_ops *)of_id->data;
 297
 298        /* Perform generic probe */
 299        err = ufshcd_pltfrm_init(pdev, vops);
 300        if (err)
 301                dev_err(dev, "ufshcd_pltfrm_init() failed %d\n", err);
 302
 303        return err;
 304}
 305
 306/**
 307 * cdns_ufs_pltfrm_remove - removes the ufs driver
 308 * @pdev: pointer to platform device handle
 309 *
 310 * Always returns 0
 311 */
 312static int cdns_ufs_pltfrm_remove(struct platform_device *pdev)
 313{
 314        struct ufs_hba *hba =  platform_get_drvdata(pdev);
 315
 316        ufshcd_remove(hba);
 317        return 0;
 318}
 319
 320static const struct dev_pm_ops cdns_ufs_dev_pm_ops = {
 321        SET_SYSTEM_SLEEP_PM_OPS(ufshcd_system_suspend, ufshcd_system_resume)
 322        SET_RUNTIME_PM_OPS(ufshcd_runtime_suspend, ufshcd_runtime_resume, NULL)
 323        .prepare         = ufshcd_suspend_prepare,
 324        .complete        = ufshcd_resume_complete,
 325};
 326
 327static struct platform_driver cdns_ufs_pltfrm_driver = {
 328        .probe  = cdns_ufs_pltfrm_probe,
 329        .remove = cdns_ufs_pltfrm_remove,
 330        .shutdown = ufshcd_pltfrm_shutdown,
 331        .driver = {
 332                .name   = "cdns-ufshcd",
 333                .pm     = &cdns_ufs_dev_pm_ops,
 334                .of_match_table = cdns_ufs_of_match,
 335        },
 336};
 337
 338module_platform_driver(cdns_ufs_pltfrm_driver);
 339
 340MODULE_AUTHOR("Jan Kotas <jank@cadence.com>");
 341MODULE_DESCRIPTION("Cadence UFS host controller platform driver");
 342MODULE_LICENSE("GPL v2");
 343MODULE_VERSION(UFSHCD_DRIVER_VERSION);
 344