// SPDX-License-Identifier: GPL-2.0-or-later /* * Support for Digigram AlpX PCI-e boards * * Copyright (c) 2024 Digigram Digital (info@digigram.com) */ #include "alpx_mtd.h" #include "alpx.h" #include "alpx_reg.h" static const u32 ALPX_FLASH_ERASED_AREA_VALUE = 0xFFFFFFFF; int alpx_mtd_read(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf) { struct alpx_device *alpx_dev = mtd->priv; size_t size; u32 page_index; u32 offset; u32 cmd; int ret; page_index = from >> ALPX_FLASH_SECTOR_SHIFT; offset = from & (ALPX_FLASH_SECTOR_SIZE-1); while (len) { if ((offset + len) > ALPX_FLASH_SECTOR_SIZE) size = ALPX_FLASH_SECTOR_SIZE - offset; else size = len; cmd = ALP_PROC_COMMAND_MAKE_P16( ALP_PROC_CMD_READ_FLASH_SECTOR, page_index); dev_dbg(alpx_dev->dev,"Reading %zu bytes Flash sector 0x%x.\n", size, page_index); mutex_lock(&alpx_dev->proc_mutex); ret = alpx_proc_cmd(alpx_dev, cmd); if (ret) { mutex_unlock(&alpx_dev->proc_mutex); dev_err(alpx_dev->dev, "cmd(CMD_READ_FLASH_SECTOR, 0x%x) failed (%d)\n", page_index, ret); return ret; } memcpy_fromio(buf, ALPX_AREA(alpx_dev, ALP, SHARED) + offset, size); mutex_unlock(&alpx_dev->proc_mutex); if (retlen) *retlen += size; buf += size; len -= size; offset = 0; page_index++; } return 0; } int alpx_mtd_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf) { struct alpx_device *alpx_dev = mtd->priv; size_t size; u32 page_index; u32 offset; u32 cmd; int ret; page_index = to >> ALPX_FLASH_SECTOR_SHIFT; offset = to & (ALPX_FLASH_SECTOR_SIZE-1); while (len) { if ((offset + len) > ALPX_FLASH_SECTOR_SIZE) size = ALPX_FLASH_SECTOR_SIZE - offset; else size = len; dev_dbg(alpx_dev->dev,"Writing %zu bytes to sector 0x%x.\n", size, page_index); mutex_lock(&alpx_dev->proc_mutex); if (size != ALPX_FLASH_SECTOR_SIZE) { /* Partial page write -> read modify write*/ cmd = ALP_PROC_COMMAND_MAKE_P16( ALP_PROC_CMD_READ_FLASH_SECTOR, page_index); ret = alpx_proc_cmd(alpx_dev, cmd); if (ret) { mutex_unlock(&alpx_dev->proc_mutex); dev_err(alpx_dev->dev, "cmd(CMD_READ_FLASH_SECTOR, 0x%x) failed (%d)\n", page_index, ret); return ret; } } memcpy_toio(ALPX_AREA(alpx_dev, ALP, SHARED) + offset, buf, size); cmd = ALP_PROC_COMMAND_MAKE_P16( ALP_PROC_CMD_WRITE_FLASH_SECTOR, page_index); ret = alpx_proc_cmd(alpx_dev, cmd); if (ret) { mutex_unlock(&alpx_dev->proc_mutex); dev_err(alpx_dev->dev, "cmd(CMD_WRITE_FLASH_SECTOR, 0x%x) failed (%d)\n", page_index, ret); return ret; } mutex_unlock(&alpx_dev->proc_mutex); if (retlen) *retlen += size; buf += size; len -= size; offset = 0; page_index++; } return 0; } int alpx_mtd_probe(struct alpx_device *alpx_dev, const struct mtd_partition* partitions, u32 partitions_size) { struct mtd_info *mtd = &alpx_dev->mtd_info; /* Setup the MTD structure */ mtd->type = MTD_RAM; mtd->flags = MTD_WRITEABLE | MTD_NO_ERASE; mtd->size = ALPX_FLASH_CHIP_SIZE; mtd->writesize = ALPX_FLASH_SECTOR_SIZE; mtd->writebufsize = mtd->writesize; mtd->priv = alpx_dev; mtd->owner = THIS_MODULE; mtd->dev.parent = alpx_dev->dev; mtd->_read = alpx_mtd_read; mtd->_write = alpx_mtd_write; return mtd_device_register(mtd, partitions, partitions_size); } int alpx_mtd_remove(struct alpx_device *alpx_dev) { return alpx_dev->variant->flash_partitions.qty ? mtd_device_unregister(&alpx_dev->mtd_info) : 0; } /* Read one Flash's page into the shared area NO LOCK DONE !!*/ static int alpx_mtd_load_one_page_into_shared_area(struct alpx_device* alpx_dev, u32 from) { u32 page_index = from >> ALPX_FLASH_SECTOR_SHIFT; u32 cmd = ALP_PROC_COMMAND_MAKE_P16( ALP_PROC_CMD_READ_FLASH_SECTOR, page_index); size_t size = ALPX_FLASH_SECTOR_SIZE; int ret; /* Check requested length below FLASH sector's size, TODO check page_index too TODO check form is aligned on page */ if (from % ALPX_FLASH_SECTOR_SIZE != 0) return -EINVAL; dev_dbg(alpx_dev->dev,"Reading %zu bytes Flash sector 0x%x (0x%x).\n", size, page_index, from); ret = alpx_proc_cmd(alpx_dev, cmd); if (ret) { dev_err(alpx_dev->dev, "cmd(CMD_READ_FLASH_SECTOR, 0x%x) failed (%d)\n", page_index, ret); return ret; } print_hex_dump_bytes("LOADED Shared area :", DUMP_PREFIX_NONE, ALPX_AREA(alpx_dev, ALP, SHARED), 32); return ret; } /* Store the shared area into the Flash at the to address. NO LOCK DONE */ static int alpx_mtd_store_one_page_into_shared_area(struct alpx_device* alpx_dev, u32 to) { u32 page_index = to >> ALPX_FLASH_SECTOR_SHIFT; u32 cmd = ALP_PROC_COMMAND_MAKE_P16( ALP_PROC_CMD_WRITE_FLASH_SECTOR, page_index); size_t size = ALPX_FLASH_SECTOR_SIZE; int ret; print_hex_dump_bytes("STORED Shared area :", DUMP_PREFIX_NONE, ALPX_AREA(alpx_dev, ALP, SHARED), 32); /* Check requested length below FLASH sector's size, TODO check page_index too TODO check form is aligned on page */ if (to % ALPX_FLASH_SECTOR_SIZE != 0) return -EINVAL; dev_dbg(alpx_dev->dev,"Storing %zu bytes to Flash sector 0x%x.\n", size, page_index); ret = alpx_proc_cmd(alpx_dev, cmd); if (ret) { dev_err(alpx_dev->dev, "cmd(ALP_PROC_CMD_WRITE_FLASH_SECTOR, 0x%x) failed (%d)\n", page_index, ret); return ret; } return ret; } int alpx_mtd_is_golden_prod_area_valid(struct alpx_device* alpx_dev) { int ret = 0; mutex_lock(&alpx_dev->proc_mutex); ret = alpx_mtd_load_one_page_into_shared_area(alpx_dev, alpx_dev->variant->flash_golden_production_base); if (!ret) { //Now check Production area validity per se : something set in and not in erased state ret = (*(u32*)ALPX_AREA(alpx_dev, ALP, SHARED)) != ALPX_FLASH_ERASED_AREA_VALUE; } else { dev_err(alpx_dev->dev, "Error %d when loading shared area from flash.\n", ret); } mutex_unlock(&alpx_dev->proc_mutex); return ret; } int alpx_mtd_replicate_prod_area(struct alpx_device* alpx_dev) { /* Read the USER's Production area and then write it to the GOLDEN'sProduction area */ //Read User int ret = 0; mutex_lock(&alpx_dev->proc_mutex); ret= alpx_mtd_load_one_page_into_shared_area(alpx_dev, alpx_dev->variant->flash_partitions.partitions[ALPX_FLASH_PARTITION_PRODUCTION_ID].offset); if (!ret) { //Write it to the GOLDEN's area ret = alpx_mtd_store_one_page_into_shared_area(alpx_dev, alpx_dev->variant->flash_golden_production_base); if (ret) { dev_err(alpx_dev->dev,"Error %d when storing the USER's Production area into GOLDEN.\n", ret); } } else { dev_err(alpx_dev->dev,"Error %d when reading the USER's Production area.\n", ret); } mutex_unlock(&alpx_dev->proc_mutex); return ret; } int alpx_mtd_read_from(struct alpx_device* alpx_dev, uint32_t from, unsigned char* to, unsigned int length) { int ret = 0; //Check : never read more than SHARE area length if (length > ALP_SHARED_SIZE) return -EINVAL; mutex_lock(&alpx_dev->proc_mutex); //Load the PRODUCTION into the shared AREA, then extract the Serial Number ret= alpx_mtd_load_one_page_into_shared_area(alpx_dev, from); if (!ret) { unsigned int idx = 0; //NO memcpy() when dealing with SHARED Area for (idx = 0 ; idx < length; ++idx) { to[idx] = *(((unsigned char*)ALPX_AREA(alpx_dev, ALP, SHARED))+idx); } print_hex_dump_bytes("READ:", DUMP_PREFIX_NONE, to, length); } else { dev_err(alpx_dev->dev, " Error 0x%x when reading Flash memory at %d.\n", ret, from); } mutex_unlock(&alpx_dev->proc_mutex); return ret; } int alpx_mtd_load_shared_from(struct alpx_device* alpx_dev, uint32_t from) { //Only one page at the moment, must be ALP_SHARED_SIZE bytes after. return alpx_mtd_load_one_page_into_shared_area(alpx_dev, from); } int alpx_mtd_read_shared(struct alpx_device* alpx_dev, uint32_t at, unsigned char* to, unsigned int length) { unsigned int idx; //NO memcpy() when dealing with SHARED Area for (idx = 0 ; idx < length; ++idx) { to[idx] = *(((unsigned char*)ALPX_AREA(alpx_dev, ALP, SHARED))+idx + at); } print_hex_dump_bytes("READ:", DUMP_PREFIX_NONE, to, length); return 0; } int alpx_mtd_clean_shared(struct alpx_device* alpx_dev) { unsigned int idx; //NO memcpy() when dealing with SHARED Area for (idx = 0 ; idx < (ALPX_FLASH_SECTOR_SIZE / sizeof(unsigned int)); ++idx) { *(unsigned int*)(ALPX_AREA(alpx_dev, ALP, SHARED)+idx) = 0; } return 0; } int alpx_mtd_is_available(struct alpx_device* alpx_dev) { //DANTE : no MTD available yet return alpx_dev->variant->model != ALPX_VARIANT_MODEL_ALPDANTE; }