// SPDX-License-Identifier: GPL-2.0 /* * Samsung Specific feature * * Copyright (C) 2021 Samsung Electronics Co., Ltd. * * Authors: * Storage Driver */ #include #include #include #include #include #include "dw_mmc-exynos.h" #include "mmc-sec-feature.h" #include "mmc-sec-sysfs.h" static ssize_t sd_detection_cmd_show(struct device *dev, struct device_attribute *attr, char *buf) { struct dw_mci *host = dev_get_drvdata(dev); struct dw_mci_exynos_priv_data *priv = host->priv; if (priv->sec_sd_slot_type > SEC_NO_DET_SD_SLOT && !gpio_is_valid(priv->cd_gpio)) goto gpio_error; if (gpio_get_value(priv->cd_gpio) ^ (host->pdata->use_gpio_invert) && (priv->sec_sd_slot_type == SEC_HYBRID_SD_SLOT)) { dev_info(host->dev, "SD slot tray Removed.\n"); return sprintf(buf, "Notray\n"); } if (host->slot && host->slot->mmc && host->slot->mmc->card) { dev_info(host->dev, "SD card inserted.\n"); return sprintf(buf, "Insert\n"); } dev_info(host->dev, "SD card removed.\n"); return sprintf(buf, "Remove\n"); gpio_error: dev_info(host->dev, "%s : External SD detect pin Error\n", __func__); return sprintf(buf, "Error\n"); } static ssize_t sd_detection_cnt_show(struct device *dev, struct device_attribute *attr, char *buf) { dev_info(dev, "%s : CD count is = %u\n", __func__, sd_op_info.card_detect_cnt); return sprintf(buf, "%u\n", sd_op_info.card_detect_cnt); } static ssize_t sd_detection_maxmode_show(struct device *dev, struct device_attribute *attr, char *buf) { struct dw_mci *host = dev_get_drvdata(dev); const char *uhs_bus_speed_mode = ""; struct device_node *np = host->dev->of_node; if (of_find_property(np, "sd-uhs-sdr104", NULL)) uhs_bus_speed_mode = "SDR104"; else if (of_find_property(np, "sd-uhs-ddr50", NULL)) uhs_bus_speed_mode = "DDR50"; else if (of_find_property(np, "sd-uhs-sdr50", NULL)) uhs_bus_speed_mode = "SDR50"; else if (of_find_property(np, "sd-uhs-sdr25", NULL)) uhs_bus_speed_mode = "SDR25"; else if (of_find_property(np, "sd-uhs-sdr12", NULL)) uhs_bus_speed_mode = "SDR12"; else uhs_bus_speed_mode = "HS"; dev_info(host->dev, "%s : Max supported Host Speed Mode = %s\n", __func__, uhs_bus_speed_mode); return sprintf(buf, "%s\n", uhs_bus_speed_mode); } static ssize_t sd_detection_curmode_show(struct device *dev, struct device_attribute *attr, char *buf) { struct dw_mci *host = dev_get_drvdata(dev); const char *uhs_bus_speed_mode = ""; static const char *const uhs_speeds[] = { [UHS_SDR12_BUS_SPEED] = "SDR12", [UHS_SDR25_BUS_SPEED] = "SDR25", [UHS_SDR50_BUS_SPEED] = "SDR50", [UHS_SDR104_BUS_SPEED] = "SDR104", [UHS_DDR50_BUS_SPEED] = "DDR50", }; if (host->slot && host->slot->mmc && host->slot->mmc->card) { if (mmc_card_uhs(host->slot->mmc->card)) uhs_bus_speed_mode = uhs_speeds[host->slot->mmc->card->sd_bus_speed]; else uhs_bus_speed_mode = "HS"; } else uhs_bus_speed_mode = "No Card"; dev_info(host->dev, "%s : Current SD Card Speed = %s\n", __func__, uhs_bus_speed_mode); return sprintf(buf, "%s\n", uhs_bus_speed_mode); } static ssize_t sdcard_summary_show(struct device *dev, struct device_attribute *attr, char *buf) { struct dw_mci *host = dev_get_drvdata(dev); struct mmc_card *card; const char *uhs_bus_speed_mode = ""; static const char *const uhs_speeds[] = { [UHS_SDR12_BUS_SPEED] = "SDR12", [UHS_SDR25_BUS_SPEED] = "SDR25", [UHS_SDR50_BUS_SPEED] = "SDR50", [UHS_SDR104_BUS_SPEED] = "SDR104", [UHS_DDR50_BUS_SPEED] = "DDR50", }; static const char *const unit[] = {"KB", "MB", "GB", "TB"}; unsigned int size, serial; int digit = 1; char ret_size[6]; if (host->slot && host->slot->mmc && host->slot->mmc->card) { card = host->slot->mmc->card; /* MANID */ /* SERIAL */ serial = card->cid.serial & (0x0000FFFF); /*SIZE*/ if (card->csd.read_blkbits == 9) /* 1 Sector = 512 Bytes */ size = (card->csd.capacity) >> 1; else if (card->csd.read_blkbits == 11) /* 1 Sector = 2048 Bytes */ size = (card->csd.capacity) << 1; else /* 1 Sector = 1024 Bytes */ size = card->csd.capacity; if (size >= 380000000 && size <= 410000000) { /* QUIRK 400GB SD Card */ sprintf(ret_size, "400GB"); } else if (size >= 190000000 && size <= 210000000) { /* QUIRK 200GB SD Card */ sprintf(ret_size, "200GB"); } else { while ((size >> 1) > 0) { size = size >> 1; digit++; } sprintf(ret_size, "%d%s", 1 << (digit%10), unit[digit/10]); } /* SPEEDMODE */ if (mmc_card_uhs(card)) uhs_bus_speed_mode = uhs_speeds[card->sd_bus_speed]; else if (mmc_card_hs(card)) uhs_bus_speed_mode = "HS"; else uhs_bus_speed_mode = "DS"; /* SUMMARY */ dev_info(host->dev, "MANID : 0x%02X, SERIAL : %04X, SIZE : %s, SPEEDMODE : %s\n", card->cid.manfid, serial, ret_size, uhs_bus_speed_mode); return sprintf(buf, "\"MANID\":\"0x%02X\",\"SERIAL\":\"%04X\""\ ",\"SIZE\":\"%s\",\"SPEEDMODE\":\"%s\",\"NOTI\":\"%d\"\n", card->cid.manfid, serial, ret_size, uhs_bus_speed_mode, sd_status_err_info[host->slot->mmc->index].noti_cnt); } else { /* SUMMARY : No SD Card Case */ dev_info(host->dev, "%s : No SD Card\n", __func__); return sprintf(buf, "\"MANID\":\"NoCard\",\"SERIAL\":\"NoCard\""\ ",\"SIZE\":\"NoCard\",\"SPEEDMODE\":\"NoCard\",\"NOTI\":\"NoCard\"\n"); } } static ssize_t sd_count_show(struct device *dev, struct device_attribute *attr, char *buf) { struct dw_mci *host = dev_get_drvdata(dev); struct mmc_host *mmc = host->slot->mmc; struct mmc_card *card = mmc->card; int host_idx = mmc->index; struct mmc_sec_err_info *err_log = sd_err_info[host_idx]; u64 total_cnt = 0; int len = 0; int i = 0; if (!card) { len = snprintf(buf, PAGE_SIZE, "no card\n"); goto out; } for (i = 0; i < 6; i++) { if (total_cnt < U64_MAX) total_cnt += err_log[i].count; } len = snprintf(buf, PAGE_SIZE, "%lld\n", total_cnt); out: return len; } static ssize_t sd_cid_show(struct device *dev, struct device_attribute *attr, char *buf) { struct dw_mci *host = dev_get_drvdata(dev); struct mmc_host *mmc = host->slot->mmc; struct mmc_card *card = mmc->card; int len = 0; if (!card) { len = snprintf(buf, PAGE_SIZE, "no card\n"); goto out; } len = snprintf(buf, PAGE_SIZE, "%08x%08x%08x%08x\n", card->raw_cid[0], card->raw_cid[1], card->raw_cid[2], card->raw_cid[3]); out: return len; } static inline void mmc_check_error_count(struct mmc_sec_err_info *err_log, unsigned long long *total_c_cnt, unsigned long long *total_t_cnt) { int i = 0; //Only sbc(0,1)/cmd(2,3)/data(4,5) is checked. for (i = 0; i < 6; i++) { if (err_log[i].err_type == -EILSEQ && *total_c_cnt < U64_MAX) *total_c_cnt += err_log[i].count; if (err_log[i].err_type == -ETIMEDOUT && *total_t_cnt < U64_MAX) *total_t_cnt += err_log[i].count; } } static ssize_t sd_health_show(struct device *dev, struct device_attribute *attr, char *buf) { struct dw_mci *host = dev_get_drvdata(dev); struct mmc_host *mmc = host->slot->mmc; struct mmc_card *card = mmc->card; int host_idx = mmc->index; struct mmc_sec_err_info *err_log = sd_err_info[host_idx]; u64 total_c_cnt = 0; u64 total_t_cnt = 0; int len = 0; if (!card) { //There should be no spaces in 'No Card'(Vold Team). len = snprintf(buf, PAGE_SIZE, "NOCARD\n"); goto out; } mmc_check_error_count(err_log, &total_c_cnt, &total_t_cnt); if (sd_status_err_info[host_idx].ge_cnt > 100 || sd_status_err_info[host_idx].ecc_cnt > 0 || sd_status_err_info[host_idx].wp_cnt > 0 || sd_status_err_info[host_idx].oor_cnt > 10 || total_t_cnt > 100 || total_c_cnt > 100) len = snprintf(buf, PAGE_SIZE, "BAD\n"); else len = snprintf(buf, PAGE_SIZE, "GOOD\n"); out: return len; } #define SEC_MMC_STATUS_ERR_INFO_GET_VALUE(member) ({ \ err_status_info->member = err_status->member - err_status_backup->member; }) static inline void mmc_check_error_count_calc_current(int host_idx, unsigned long long *total_c_cnt, unsigned long long *total_t_cnt, struct mmc_sec_status_err_info *err_status_info) { struct mmc_sec_err_info *err_log = sd_err_info[host_idx]; struct mmc_sec_err_info *err_log_backup = sd_err_info_backup[host_idx]; struct mmc_sec_status_err_info *err_status = &sd_status_err_info[host_idx]; struct mmc_sec_status_err_info *err_status_backup = &sd_status_err_info_backup[host_idx]; int i = 0; //Only sbc(0,1)/cmd(2,3)/data(4,5) is checked. for (i = 0; i < 6; i++) { if (err_log[i].err_type == -EILSEQ && *total_c_cnt < U64_MAX) *total_c_cnt += (err_log[i].count - err_log_backup[i].count); if (err_log[i].err_type == -ETIMEDOUT && *total_t_cnt < U64_MAX) *total_t_cnt += (err_log[i].count - err_log_backup[i].count); } SEC_MMC_STATUS_ERR_INFO_GET_VALUE(ge_cnt); SEC_MMC_STATUS_ERR_INFO_GET_VALUE(cc_cnt); SEC_MMC_STATUS_ERR_INFO_GET_VALUE(ecc_cnt); SEC_MMC_STATUS_ERR_INFO_GET_VALUE(wp_cnt); SEC_MMC_STATUS_ERR_INFO_GET_VALUE(oor_cnt); SEC_MMC_STATUS_ERR_INFO_GET_VALUE(halt_cnt); SEC_MMC_STATUS_ERR_INFO_GET_VALUE(cq_cnt); SEC_MMC_STATUS_ERR_INFO_GET_VALUE(rpmb_cnt); SEC_MMC_STATUS_ERR_INFO_GET_VALUE(noti_cnt); } #define SEC_MMC_STATUS_ERR_INFO_BACKUP(member) ({ \ err_status_backup->member = err_status->member; }) static inline void mmc_backup_err_info(int host_idx) { struct mmc_sec_err_info *err_log = sd_err_info[host_idx]; struct mmc_sec_err_info *err_log_backup = sd_err_info_backup[host_idx]; struct mmc_sec_status_err_info *err_status = &sd_status_err_info[host_idx]; struct mmc_sec_status_err_info *err_status_backup = &sd_status_err_info_backup[host_idx]; int i = 0; // save current error count for (i = 0; i < MAX_ERR_LOG_INDEX; i++) err_log_backup[i].count = err_log[i].count; SEC_MMC_STATUS_ERR_INFO_BACKUP(ge_cnt); SEC_MMC_STATUS_ERR_INFO_BACKUP(cc_cnt); SEC_MMC_STATUS_ERR_INFO_BACKUP(ecc_cnt); SEC_MMC_STATUS_ERR_INFO_BACKUP(wp_cnt); SEC_MMC_STATUS_ERR_INFO_BACKUP(oor_cnt); SEC_MMC_STATUS_ERR_INFO_BACKUP(halt_cnt); SEC_MMC_STATUS_ERR_INFO_BACKUP(cq_cnt); SEC_MMC_STATUS_ERR_INFO_BACKUP(rpmb_cnt); SEC_MMC_STATUS_ERR_INFO_BACKUP(noti_cnt); } static ssize_t sd_data_show(struct device *dev, struct device_attribute *attr, char *buf) { struct dw_mci *host = dev_get_drvdata(dev); struct mmc_host *mmc = host->slot->mmc; struct mmc_card *card = mmc->card; int host_idx = mmc->index; struct mmc_sec_status_err_info status_err_info; u64 total_c_cnt = 0; u64 total_t_cnt = 0; int len = 0; memset(&status_err_info, 0, sizeof(struct mmc_sec_status_err_info)); if (!card) { len = snprintf(buf, PAGE_SIZE, "\"GE\":\"0\",\"CC\":\"0\",\"ECC\":\"0\",\"WP\":\"0\"," \ "\"OOR\":\"0\",\"CRC\":\"0\",\"TMO\":\"0\"\n"); goto out; } mmc_check_error_count_calc_current(host_idx, &total_c_cnt, &total_t_cnt, &status_err_info); len = snprintf(buf, PAGE_SIZE, "\"GE\":\"%d\",\"CC\":\"%d\",\"ECC\":\"%d\",\"WP\":\"%d\"," \ "\"OOR\":\"%d\",\"CRC\":\"%lld\",\"TMO\":\"%lld\"\n", status_err_info.ge_cnt, status_err_info.cc_cnt, status_err_info.ecc_cnt, status_err_info.wp_cnt, status_err_info.oor_cnt, total_c_cnt, total_t_cnt); out: return len; } static ssize_t sd_data_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct dw_mci *host = dev_get_drvdata(dev); struct mmc_host *mmc = host->slot->mmc; struct mmc_card *card = mmc->card; int host_idx = mmc->index; if (!card) return -ENODEV; if ((buf[0] != 'C' && buf[0] != 'c') || (count != 1)) return -EINVAL; mmc_backup_err_info(host_idx); return count; } static ssize_t mmc_error_count_show(struct device *dev, struct device_attribute *attr, char *buf) { struct dw_mci *host = dev_get_drvdata(dev); struct mmc_host *mmc = host->slot->mmc; struct mmc_card *card = mmc->card; int host_idx = mmc->index; struct mmc_sec_err_info *err_log = sd_err_info[host_idx]; u64 total_c_cnt = 0; u64 total_t_cnt = 0; int total_len = 0; int i = 0; if (!card) { total_len = snprintf(buf, PAGE_SIZE, "It's no card error..\n"); goto out; } total_len += snprintf(buf, PAGE_SIZE, "type : err status: first_issue_time: last_issue_time: count\n"); for (i = 0; i < MAX_ERR_LOG_INDEX; i++) { total_len += snprintf(buf + total_len, PAGE_SIZE - total_len, "%5s:%4d 0x%08x %16llu, %16llu, %10d\n", err_log[i].type, err_log[i].err_type, err_log[i].status, err_log[i].first_issue_time, err_log[i].last_issue_time, err_log[i].count); } mmc_check_error_count(err_log, &total_c_cnt, &total_t_cnt); total_len += snprintf(buf + total_len, PAGE_SIZE - total_len, "GE:%d,CC:%d,ECC:%d,WP:%d,OOR:%d,CRC:%lld,TMO:%lld,HALT:%d,CQEN:%d,RPMB:%d\n", sd_status_err_info[host_idx].ge_cnt, sd_status_err_info[host_idx].cc_cnt, sd_status_err_info[host_idx].ecc_cnt, sd_status_err_info[host_idx].wp_cnt, sd_status_err_info[host_idx].oor_cnt, total_c_cnt, total_t_cnt, sd_status_err_info[host_idx].halt_cnt, sd_status_err_info[host_idx].cq_cnt, sd_status_err_info[host_idx].rpmb_cnt); out: return total_len; } static DEVICE_ATTR(status, 0444, sd_detection_cmd_show, NULL); static DEVICE_ATTR(cd_cnt, 0444, sd_detection_cnt_show, NULL); static DEVICE_ATTR(max_mode, 0444, sd_detection_maxmode_show, NULL); static DEVICE_ATTR(current_mode, 0444, sd_detection_curmode_show, NULL); static DEVICE_ATTR(sdcard_summary, 0444, sdcard_summary_show, NULL); static DEVICE_ATTR(sd_count, 0444, sd_count_show, NULL); static DEVICE_ATTR(data, 0444, sd_cid_show, NULL); static DEVICE_ATTR(fc, 0444, sd_health_show, NULL); static DEVICE_ATTR(sd_data, 0664, sd_data_show, sd_data_store); static DEVICE_ATTR(err_count, 0444, mmc_error_count_show, NULL); static void mmc_sec_device_create_file(struct device *dev, const struct device_attribute *dev_attr) { if (device_create_file(dev, dev_attr) < 0) pr_err("%s : Failed to create device file(%s)!\n", __func__, dev_attr->attr.name); } void mmc_sec_init_sysfs(struct dw_mci *host) { struct dw_mci_exynos_priv_data *priv = host->priv; /* Initialize cd_cnt to 0. */ sd_op_info.card_detect_cnt = 0; if ((priv->sec_sd_slot_type) >= SEC_NO_DET_SD_SLOT) { if (!sd_detection_cmd_dev) { sd_detection_cmd_dev = sec_device_create(host, "sdcard"); if (IS_ERR(sd_detection_cmd_dev)) { pr_err("Fail to create sysfs dev\n"); goto failed_sdcard; } mmc_sec_device_create_file(sd_detection_cmd_dev, &dev_attr_status); mmc_sec_device_create_file(sd_detection_cmd_dev, &dev_attr_max_mode); mmc_sec_device_create_file(sd_detection_cmd_dev, &dev_attr_current_mode); mmc_sec_device_create_file(sd_detection_cmd_dev, &dev_attr_cd_cnt); mmc_sec_device_create_file(sd_detection_cmd_dev, &dev_attr_sdcard_summary); mmc_sec_device_create_file(sd_detection_cmd_dev, &dev_attr_err_count); } failed_sdcard: if (!sd_info_cmd_dev) { sd_info_cmd_dev = sec_device_create(host, "sdinfo"); if (IS_ERR(sd_info_cmd_dev)) { pr_err("Fail to create sysfs dev\n"); goto failed_sdinfo; } mmc_sec_device_create_file(sd_info_cmd_dev, &dev_attr_sd_count); mmc_sec_device_create_file(sd_info_cmd_dev, &dev_attr_data); mmc_sec_device_create_file(sd_info_cmd_dev, &dev_attr_fc); } failed_sdinfo: if (!sd_data_cmd_dev) { sd_data_cmd_dev = sec_device_create(host, "sddata"); if (IS_ERR(sd_data_cmd_dev)) { pr_err("Fail to create sysfs dev\n"); return; } mmc_sec_device_create_file(sd_data_cmd_dev, &dev_attr_sd_data); } } }