From: Petr Hodina <[email protected]> Infrastructure (ufs_quirks.h): - struct ufs_dev_quirk plus UFS_FIX()/UFS_FIX_REVISION() helpers. - ufshcd_fixup_dev_quirks() applies the table (and sets NO_PURGE for devices older than UFS_PURGE_SPEC_VER) from ufs_start(), before power mode negotiation so link quirks take effect. - hba gains a dev_quirks field, distinct from the host controller quirks (hba->quirks).
Signed-off-by: Petr Hodina <[email protected]> --- drivers/ufs/ufs-uclass.c | 52 +++++++++++++++++++++++++++++ drivers/ufs/ufs.h | 2 ++ drivers/ufs/ufs_quirks.h | 87 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 141 insertions(+) diff --git a/drivers/ufs/ufs-uclass.c b/drivers/ufs/ufs-uclass.c index 56118a0ee46..05b95c510b4 100644 --- a/drivers/ufs/ufs-uclass.c +++ b/drivers/ufs/ufs-uclass.c @@ -29,6 +29,7 @@ #include <linux/dma-mapping.h> #include "ufs.h" +#include "ufs_quirks.h" #define UFSHCD_ENABLE_INTRS (UTP_TRANSFER_REQ_COMPL |\ UTP_TASK_REQ_COMPL |\ @@ -1866,6 +1867,54 @@ out: return err; } +/* + * UFS device quirk table. Entries are matched by manufacturer id, model and + * fw revision; UFS_ANY_VENDOR / UFS_ANY_MODEL / UFS_ANY_VER act as wildcards. + */ +static const struct ufs_dev_quirk ufs_fixups[] = { + /* Sony Yoshino/Tama: block PURGE/UNMAP to avoid bricking */ + UFS_FIX_REVISION(UFS_VENDOR_SKHYNIX, UFS_MODEL_HYNIX_32GB, + UFS_REVISION_HYNIX, UFS_DEVICE_QUIRK_NO_PURGE), + UFS_FIX_REVISION(UFS_VENDOR_SKHYNIX, UFS_MODEL_HYNIX_64GB, + UFS_REVISION_HYNIX, UFS_DEVICE_QUIRK_NO_PURGE), + UFS_FIX_REVISION(UFS_VENDOR_SAMSUNG, UFS_MODEL_SAMSUNG_64GB, + UFS_REVISION_SAMSUNG, UFS_DEVICE_QUIRK_NO_PURGE), + END_FIX +}; + +/** + * ufshcd_fixup_dev_quirks - apply device-specific quirks + * + * Sets hba->dev_quirks based on the UFS spec version and the per-device + * fixup table above. + */ +static void ufshcd_fixup_dev_quirks(struct ufs_hba *hba, + struct ufs_dev_desc *dev_desc) +{ + const struct ufs_dev_quirk *f; + + /* + * Devices implementing an older UFS spec must not be issued PURGE / + * UNMAP, doing so erases the bootloader on affected platforms. + */ + if (dev_desc->wspecversion < UFS_PURGE_SPEC_VER) + hba->dev_quirks |= UFS_DEVICE_QUIRK_NO_PURGE; + + for (f = ufs_fixups; f->quirk; f++) { + if ((f->wmanufacturerid == dev_desc->wmanufacturerid || + f->wmanufacturerid == UFS_ANY_VENDOR) && + (str_prfx_equal(f->model, dev_desc->model) || + !strcmp(f->model, UFS_ANY_MODEL)) && + (str_prfx_equal(f->revision, dev_desc->revision) || + !strcmp(f->revision, UFS_ANY_VER))) + hba->dev_quirks |= f->quirk; + } + + if (hba->dev_quirks) + dev_dbg(hba->dev, "%s: applied device quirks 0x%08x\n", + __func__, hba->dev_quirks); +} + struct ufs_ref_clk { unsigned long freq_hz; enum ufs_ref_clk_freq val; @@ -2175,6 +2224,9 @@ static int ufs_start(struct ufs_hba *hba) return ret; } + /* Apply device-specific quirks before negotiating the power mode */ + ufshcd_fixup_dev_quirks(hba, &card); + ufshcd_set_dev_ref_clk(hba); if (ufshcd_get_max_pwr_mode(hba)) { diff --git a/drivers/ufs/ufs.h b/drivers/ufs/ufs.h index e3368a516e4..d4bcb7a1773 100644 --- a/drivers/ufs/ufs.h +++ b/drivers/ufs/ufs.h @@ -707,6 +707,8 @@ struct ufs_hba { u32 version; u32 intr_mask; enum ufshcd_quirks quirks; + /* UFS device (not host controller) quirks, see ufs_quirks.h */ + u32 dev_quirks; /* Virtual memory reference */ struct utp_transfer_cmd_desc *ucdl; diff --git a/drivers/ufs/ufs_quirks.h b/drivers/ufs/ufs_quirks.h new file mode 100644 index 00000000000..f5aa47722db --- /dev/null +++ b/drivers/ufs/ufs_quirks.h @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * UFS device quirks + * + * Ported from the Linux kernel (include/ufs/ufs_quirks.h) and the + * Sony Yoshino/Tama downstream UFS quirks/hacks. + */ +#ifndef _UFS_QUIRKS_H +#define _UFS_QUIRKS_H + +#include <linux/bitops.h> +#include <linux/string.h> + +/* true if s1 (the fixup pattern) is a prefix of s2 (the device value) */ +static inline bool str_prfx_equal(const char *s1, const char *s2) +{ + return strncmp(s1, s2, strlen(s1)) == 0; +} + +#define UFS_ANY_VENDOR 0xFFFF +#define UFS_ANY_MODEL "ANY_MODEL" +#define UFS_ANY_VER "ANY_VER" + +#define UFS_VENDOR_TOSHIBA 0x198 +#define UFS_VENDOR_SAMSUNG 0x1CE +#define UFS_VENDOR_SKHYNIX 0x1AD +#define UFS_VENDOR_MICRON 0x12C +#define UFS_VENDOR_WDC 0x145 + +/* UFS Samsung models */ +#define UFS_MODEL_SAMSUNG_64GB "KLUDG4U1EA-B0C1" +#define UFS_REVISION_SAMSUNG "0100" + +/* UFS SK Hynix models */ +#define UFS_MODEL_HYNIX_32GB "hB8aL1" +#define UFS_MODEL_HYNIX_64GB "hC8aL1" +#define UFS_REVISION_HYNIX "D001" + +/* + * Devices with a UFS spec version older than this must not be issued PURGE / + * UNMAP (secure erase) operations: on the Sony Yoshino/Tama platforms doing so + * erases the bootloader and permanently bricks the device. + */ +#define UFS_PURGE_SPEC_VER 0x210 + +/** + * struct ufs_dev_quirk - ufs device quirk info + * @wmanufacturerid: card details (UFS_ANY_VENDOR matches any vendor) + * @model: card model (UFS_ANY_MODEL matches any model) + * @revision: card fw revision (UFS_ANY_VER matches any revision) + * @quirk: device quirk bitmask + */ +struct ufs_dev_quirk { + u16 wmanufacturerid; + const char *model; + const char *revision; + unsigned int quirk; +}; + +/* terminator for the fixup table */ +#define END_FIX { 0, NULL, NULL, 0 } + +/* add a quirk for any revision of a specific model */ +#define UFS_FIX(_vendor, _model, _quirk) { \ + .wmanufacturerid = (_vendor), \ + .model = (_model), \ + .revision = (UFS_ANY_VER), \ + .quirk = (_quirk), \ +} + +/* add a quirk for a specific model and fw revision */ +#define UFS_FIX_REVISION(_vendor, _model, _revision, _quirk) { \ + .wmanufacturerid = (_vendor), \ + .model = (_model), \ + .revision = (_revision), \ + .quirk = (_quirk), \ +} + +/* + * Some Sony UFS devices must not use the PURGE / UNMAP (secure erase) + * operation as it would erase the bootloader and permanently brick the + * device. This quirk disables PURGE-related functionality for affected + * devices. + */ +#define UFS_DEVICE_QUIRK_NO_PURGE BIT(0) + +#endif /* _UFS_QUIRKS_H */ -- 2.55.0
