/* * Copyright (c) 2013-2015, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include "ufshcd.h" #include "ufs_quirks.h" static struct ufs_card_fix ufs_fixups[] = { /* UFS cards deviations table */ UFS_FIX(UFS_VENDOR_SAMSUNG, UFS_ANY_MODEL, UFS_DEVICE_NO_VCCQ), UFS_FIX(UFS_VENDOR_SAMSUNG, UFS_ANY_MODEL, UFS_DEVICE_QUIRK_RECOVERY_FROM_DL_NAC_ERRORS), UFS_FIX(UFS_VENDOR_SAMSUNG, UFS_ANY_MODEL, UFS_DEVICE_NO_FASTAUTO), UFS_FIX(UFS_VENDOR_TOSHIBA, "THGLF2G9C8KBADG", UFS_DEVICE_QUIRK_PA_TACTIVATE), UFS_FIX(UFS_VENDOR_TOSHIBA, "THGLF2G9D8KBADG", UFS_DEVICE_QUIRK_PA_TACTIVATE), UFS_FIX(UFS_VENDOR_SAMSUNG, UFS_ANY_MODEL, UFS_DEVICE_QUIRK_HOST_PA_TACTIVATE), UFS_FIX(UFS_VENDOR_HYNIX, UFS_ANY_MODEL, UFS_DEVICE_QUIRK_HOST_PA_SAVECONFIGTIME), END_FIX }; static int ufs_get_device_info(struct ufs_hba *hba, struct ufs_card_info *card_data) { int err; u8 model_index; u8 str_desc_buf[QUERY_DESC_STRING_MAX_SIZE + 1]; u8 desc_buf[QUERY_DESC_DEVICE_MAX_SIZE]; err = ufshcd_read_device_desc(hba, desc_buf, QUERY_DESC_DEVICE_MAX_SIZE); if (err) goto out; /* * getting vendor (manufacturerID) and Bank Index in big endian * format */ card_data->wmanufacturerid = desc_buf[DEVICE_DESC_PARAM_MANF_ID] << 8 | desc_buf[DEVICE_DESC_PARAM_MANF_ID + 1]; model_index = desc_buf[DEVICE_DESC_PARAM_PRDCT_NAME]; memset(str_desc_buf, 0, QUERY_DESC_STRING_MAX_SIZE); err = ufshcd_read_string_desc(hba, model_index, str_desc_buf, QUERY_DESC_STRING_MAX_SIZE, ASCII_STD); if (err) goto out; str_desc_buf[QUERY_DESC_STRING_MAX_SIZE] = '\0'; strlcpy(card_data->model, (str_desc_buf + QUERY_DESC_HDR_SIZE), min_t(u8, str_desc_buf[QUERY_DESC_LENGTH_OFFSET], MAX_MODEL_LEN)); /* Null terminate the model string */ card_data->model[MAX_MODEL_LEN] = '\0'; out: return err; } void ufs_advertise_fixup_device(struct ufs_hba *hba) { int err; struct ufs_card_fix *f; struct ufs_card_info card_data; card_data.wmanufacturerid = 0; card_data.model = kmalloc(MAX_MODEL_LEN + 1, GFP_KERNEL); if (!card_data.model) goto out; /* get device data*/ err = ufs_get_device_info(hba, &card_data); if (err) { dev_err(hba->dev, "%s: Failed getting device info\n", __func__); goto out; } for (f = ufs_fixups; f->quirk; f++) { /* if same wmanufacturerid */ if (((f->card.wmanufacturerid == card_data.wmanufacturerid) || (f->card.wmanufacturerid == UFS_ANY_VENDOR)) && /* and same model */ (STR_PRFX_EQUAL(f->card.model, card_data.model) || !strcmp(f->card.model, UFS_ANY_MODEL))) /* update quirks */ hba->dev_quirks |= f->quirk; } out: kfree(card_data.model); }