ufshcd-pci.c 8.7 KB
Newer Older
1
// SPDX-License-Identifier: GPL-2.0-or-later
2 3 4 5 6 7 8 9 10 11 12 13 14
/*
 * Universal Flash Storage Host controller PCI glue driver
 *
 * This code is based on drivers/scsi/ufs/ufshcd-pci.c
 * Copyright (C) 2011-2013 Samsung India Software Operations
 *
 * Authors:
 *	Santosh Yaraganavi <santosh.sy@samsung.com>
 *	Vinayak Holikatti <h.vinayak@samsung.com>
 */

#include "ufshcd.h"
#include <linux/pci.h>
15
#include <linux/pm_runtime.h>
16 17 18 19 20 21 22 23
#include <linux/pm_qos.h>
#include <linux/debugfs.h>

struct intel_host {
	u32		active_ltr;
	u32		idle_ltr;
	struct dentry	*debugfs_root;
};
24

25 26 27 28 29 30 31
static int ufs_intel_disable_lcc(struct ufs_hba *hba)
{
	u32 attr = UIC_ARG_MIB(PA_LOCAL_TX_LCC_ENABLE);
	u32 lcc_enable = 0;

	ufshcd_dme_get(hba, attr, &lcc_enable);
	if (lcc_enable)
32
		ufshcd_disable_host_tx_lcc(hba);
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54

	return 0;
}

static int ufs_intel_link_startup_notify(struct ufs_hba *hba,
					 enum ufs_notify_change_status status)
{
	int err = 0;

	switch (status) {
	case PRE_CHANGE:
		err = ufs_intel_disable_lcc(hba);
		break;
	case POST_CHANGE:
		break;
	default:
		break;
	}

	return err;
}

55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
#define INTEL_ACTIVELTR		0x804
#define INTEL_IDLELTR		0x808

#define INTEL_LTR_REQ		BIT(15)
#define INTEL_LTR_SCALE_MASK	GENMASK(11, 10)
#define INTEL_LTR_SCALE_1US	(2 << 10)
#define INTEL_LTR_SCALE_32US	(3 << 10)
#define INTEL_LTR_VALUE_MASK	GENMASK(9, 0)

static void intel_cache_ltr(struct ufs_hba *hba)
{
	struct intel_host *host = ufshcd_get_variant(hba);

	host->active_ltr = readl(hba->mmio_base + INTEL_ACTIVELTR);
	host->idle_ltr = readl(hba->mmio_base + INTEL_IDLELTR);
}

static void intel_ltr_set(struct device *dev, s32 val)
{
	struct ufs_hba *hba = dev_get_drvdata(dev);
	struct intel_host *host = ufshcd_get_variant(hba);
	u32 ltr;

	pm_runtime_get_sync(dev);

	/*
	 * Program latency tolerance (LTR) accordingly what has been asked
	 * by the PM QoS layer or disable it in case we were passed
	 * negative value or PM_QOS_LATENCY_ANY.
	 */
	ltr = readl(hba->mmio_base + INTEL_ACTIVELTR);

	if (val == PM_QOS_LATENCY_ANY || val < 0) {
		ltr &= ~INTEL_LTR_REQ;
	} else {
		ltr |= INTEL_LTR_REQ;
		ltr &= ~INTEL_LTR_SCALE_MASK;
		ltr &= ~INTEL_LTR_VALUE_MASK;

		if (val > INTEL_LTR_VALUE_MASK) {
			val >>= 5;
			if (val > INTEL_LTR_VALUE_MASK)
				val = INTEL_LTR_VALUE_MASK;
			ltr |= INTEL_LTR_SCALE_32US | val;
		} else {
			ltr |= INTEL_LTR_SCALE_1US | val;
		}
	}

	if (ltr == host->active_ltr)
		goto out;

	writel(ltr, hba->mmio_base + INTEL_ACTIVELTR);
	writel(ltr, hba->mmio_base + INTEL_IDLELTR);

	/* Cache the values into intel_host structure */
	intel_cache_ltr(hba);
out:
	pm_runtime_put(dev);
}

static void intel_ltr_expose(struct device *dev)
{
	dev->power.set_latency_tolerance = intel_ltr_set;
	dev_pm_qos_expose_latency_tolerance(dev);
}

static void intel_ltr_hide(struct device *dev)
{
	dev_pm_qos_hide_latency_tolerance(dev);
	dev->power.set_latency_tolerance = NULL;
}

static void intel_add_debugfs(struct ufs_hba *hba)
{
	struct dentry *dir = debugfs_create_dir(dev_name(hba->dev), NULL);
	struct intel_host *host = ufshcd_get_variant(hba);

	intel_cache_ltr(hba);

	host->debugfs_root = dir;
	debugfs_create_x32("active_ltr", 0444, dir, &host->active_ltr);
	debugfs_create_x32("idle_ltr", 0444, dir, &host->idle_ltr);
}

static void intel_remove_debugfs(struct ufs_hba *hba)
{
	struct intel_host *host = ufshcd_get_variant(hba);

	debugfs_remove_recursive(host->debugfs_root);
}

static int ufs_intel_common_init(struct ufs_hba *hba)
{
	struct intel_host *host;

	host = devm_kzalloc(hba->dev, sizeof(*host), GFP_KERNEL);
	if (!host)
		return -ENOMEM;
	ufshcd_set_variant(hba, host);
	intel_ltr_expose(hba->dev);
	intel_add_debugfs(hba);
	return 0;
}

static void ufs_intel_common_exit(struct ufs_hba *hba)
{
	intel_remove_debugfs(hba);
	intel_ltr_hide(hba->dev);
}

166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
static int ufs_intel_resume(struct ufs_hba *hba, enum ufs_pm_op op)
{
	/*
	 * To support S4 (suspend-to-disk) with spm_lvl other than 5, the base
	 * address registers must be restored because the restore kernel can
	 * have used different addresses.
	 */
	ufshcd_writel(hba, lower_32_bits(hba->utrdl_dma_addr),
		      REG_UTP_TRANSFER_REQ_LIST_BASE_L);
	ufshcd_writel(hba, upper_32_bits(hba->utrdl_dma_addr),
		      REG_UTP_TRANSFER_REQ_LIST_BASE_H);
	ufshcd_writel(hba, lower_32_bits(hba->utmrdl_dma_addr),
		      REG_UTP_TASK_REQ_LIST_BASE_L);
	ufshcd_writel(hba, upper_32_bits(hba->utmrdl_dma_addr),
		      REG_UTP_TASK_REQ_LIST_BASE_H);
	return 0;
}

184 185 186
static int ufs_intel_ehl_init(struct ufs_hba *hba)
{
	hba->quirks |= UFSHCD_QUIRK_BROKEN_AUTO_HIBERN8;
187
	return ufs_intel_common_init(hba);
188 189
}

190 191
static struct ufs_hba_variant_ops ufs_intel_cnl_hba_vops = {
	.name                   = "intel-pci",
192 193
	.init			= ufs_intel_common_init,
	.exit			= ufs_intel_common_exit,
194
	.link_startup_notify	= ufs_intel_link_startup_notify,
195
	.resume			= ufs_intel_resume,
196 197
};

198 199 200
static struct ufs_hba_variant_ops ufs_intel_ehl_hba_vops = {
	.name                   = "intel-pci",
	.init			= ufs_intel_ehl_init,
201
	.exit			= ufs_intel_common_exit,
202
	.link_startup_notify	= ufs_intel_link_startup_notify,
203
	.resume			= ufs_intel_resume,
204 205
};

A
Adrian Hunter 已提交
206
#ifdef CONFIG_PM_SLEEP
207 208
/**
 * ufshcd_pci_suspend - suspend power management function
209
 * @dev: pointer to PCI device handle
210
 *
211 212
 * Returns 0 if successful
 * Returns non-zero otherwise
213
 */
214
static int ufshcd_pci_suspend(struct device *dev)
215
{
216
	return ufshcd_system_suspend(dev_get_drvdata(dev));
217 218 219 220
}

/**
 * ufshcd_pci_resume - resume power management function
221
 * @dev: pointer to PCI device handle
222
 *
223 224
 * Returns 0 if successful
 * Returns non-zero otherwise
225
 */
226
static int ufshcd_pci_resume(struct device *dev)
227
{
228
	return ufshcd_system_resume(dev_get_drvdata(dev));
229
}
A
Adrian Hunter 已提交
230
#endif /* !CONFIG_PM_SLEEP */
231

A
Adrian Hunter 已提交
232
#ifdef CONFIG_PM
233 234
static int ufshcd_pci_runtime_suspend(struct device *dev)
{
235
	return ufshcd_runtime_suspend(dev_get_drvdata(dev));
236 237 238
}
static int ufshcd_pci_runtime_resume(struct device *dev)
{
239
	return ufshcd_runtime_resume(dev_get_drvdata(dev));
240 241 242
}
static int ufshcd_pci_runtime_idle(struct device *dev)
{
243
	return ufshcd_runtime_idle(dev_get_drvdata(dev));
244
}
A
Adrian Hunter 已提交
245
#endif /* !CONFIG_PM */
246

247 248 249 250 251 252
/**
 * ufshcd_pci_shutdown - main function to put the controller in reset state
 * @pdev: pointer to PCI device handle
 */
static void ufshcd_pci_shutdown(struct pci_dev *pdev)
{
253
	ufshcd_shutdown((struct ufs_hba *)pci_get_drvdata(pdev));
254 255 256 257 258
}

/**
 * ufshcd_pci_remove - de-allocate PCI/SCSI host and host memory space
 *		data structure memory
259
 * @pdev: pointer to PCI handle
260 261 262 263 264
 */
static void ufshcd_pci_remove(struct pci_dev *pdev)
{
	struct ufs_hba *hba = pci_get_drvdata(pdev);

265 266
	pm_runtime_forbid(&pdev->dev);
	pm_runtime_get_noresume(&pdev->dev);
267
	ufshcd_remove(hba);
268
	ufshcd_dealloc_host(hba);
269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284
}

/**
 * ufshcd_pci_probe - probe routine of the driver
 * @pdev: pointer to PCI device handle
 * @id: PCI device id
 *
 * Returns 0 on success, non-zero value on failure
 */
static int
ufshcd_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
	struct ufs_hba *hba;
	void __iomem *mmio_base;
	int err;

285
	err = pcim_enable_device(pdev);
286
	if (err) {
287 288
		dev_err(&pdev->dev, "pcim_enable_device failed\n");
		return err;
289 290 291 292
	}

	pci_set_master(pdev);

293
	err = pcim_iomap_regions(pdev, 1 << 0, UFSHCD);
294
	if (err < 0) {
295 296
		dev_err(&pdev->dev, "request and iomap failed\n");
		return err;
297 298
	}

299
	mmio_base = pcim_iomap_table(pdev)[0];
300

301 302 303 304 305 306
	err = ufshcd_alloc_host(&pdev->dev, &hba);
	if (err) {
		dev_err(&pdev->dev, "Allocation failed\n");
		return err;
	}

307 308
	pci_set_drvdata(pdev, hba);

309 310
	hba->vops = (struct ufs_hba_variant_ops *)id->driver_data;

311
	err = ufshcd_init(hba, mmio_base, pdev->irq);
312 313
	if (err) {
		dev_err(&pdev->dev, "Initialization failed\n");
314
		ufshcd_dealloc_host(hba);
315
		return err;
316 317
	}

318 319
	pm_runtime_put_noidle(&pdev->dev);
	pm_runtime_allow(&pdev->dev);
320 321 322 323

	return 0;
}

324
static const struct dev_pm_ops ufshcd_pci_pm_ops = {
A
Adrian Hunter 已提交
325 326 327 328 329
	SET_SYSTEM_SLEEP_PM_OPS(ufshcd_pci_suspend,
				ufshcd_pci_resume)
	SET_RUNTIME_PM_OPS(ufshcd_pci_runtime_suspend,
			   ufshcd_pci_runtime_resume,
			   ufshcd_pci_runtime_idle)
330 331
};

332
static const struct pci_device_id ufshcd_pci_tbl[] = {
333
	{ PCI_VENDOR_ID_SAMSUNG, 0xC00C, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
334
	{ PCI_VDEVICE(INTEL, 0x9DFA), (kernel_ulong_t)&ufs_intel_cnl_hba_vops },
335 336
	{ PCI_VDEVICE(INTEL, 0x4B41), (kernel_ulong_t)&ufs_intel_ehl_hba_vops },
	{ PCI_VDEVICE(INTEL, 0x4B43), (kernel_ulong_t)&ufs_intel_ehl_hba_vops },
337 338 339 340 341 342 343 344 345 346 347
	{ }	/* terminate list */
};

MODULE_DEVICE_TABLE(pci, ufshcd_pci_tbl);

static struct pci_driver ufshcd_pci_driver = {
	.name = UFSHCD,
	.id_table = ufshcd_pci_tbl,
	.probe = ufshcd_pci_probe,
	.remove = ufshcd_pci_remove,
	.shutdown = ufshcd_pci_shutdown,
348 349 350
	.driver = {
		.pm = &ufshcd_pci_pm_ops
	},
351 352 353 354 355 356 357 358 359
};

module_pci_driver(ufshcd_pci_driver);

MODULE_AUTHOR("Santosh Yaragnavi <santosh.sy@samsung.com>");
MODULE_AUTHOR("Vinayak Holikatti <h.vinayak@samsung.com>");
MODULE_DESCRIPTION("UFS host controller PCI glue driver");
MODULE_LICENSE("GPL");
MODULE_VERSION(UFSHCD_DRIVER_VERSION);