| /**************************************************************************** |
| * arch/arm/src/lc823450/lc823450_mtd.c |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. The |
| * ASF licenses this file to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance with the |
| * License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations |
| * under the License. |
| * |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Included Files |
| ****************************************************************************/ |
| |
| #include <nuttx/config.h> |
| |
| #include <sys/types.h> |
| |
| #include <inttypes.h> |
| #include <stdio.h> |
| #include <stdint.h> |
| #include <assert.h> |
| #include <errno.h> |
| #include <debug.h> |
| |
| #include <nuttx/kmalloc.h> |
| #include <nuttx/fs/ioctl.h> |
| #include <nuttx/mtd/mtd.h> |
| #include <nuttx/mutex.h> |
| #include <arch/board/board.h> |
| |
| #include "lc823450_mtd.h" |
| #include "lc823450_mmcl.h" |
| #include "lc823450_sdc.h" |
| #include "lc823450_sddrv_if.h" |
| #include "lc823450_clockconfig.h" |
| |
| /**************************************************************************** |
| * Pre-processor Definitions |
| ****************************************************************************/ |
| |
| #if CONFIG_MTD_DEV_MAX > 2 |
| # error "MTD: Too many MTD device" |
| #endif |
| |
| #if CONFIG_MTD_DEV_MAX == 2 |
| # if (CONFIG_MTD_DEVNO_EMMC == CONFIG_MTD_DEVNO_SDC) |
| # error "MTD: Invalid devno specified" |
| # endif |
| #endif |
| |
| /**************************************************************************** |
| * Private Types |
| ****************************************************************************/ |
| |
| /* This type represents the state of the MTD device. The struct mtd_dev_s |
| * must appear at the beginning of the definition so that you can freely |
| * cast between pointers to struct mtd_dev_s and struct skel_dev_s. |
| */ |
| |
| struct lc823450_mtd_dev_s |
| { |
| struct mtd_dev_s mtd; |
| |
| /* Other implementation specific data may follow here */ |
| |
| mutex_t lock; /* Assures mutually exclusive access to the slot */ |
| uint32_t nblocks; /* Number of blocks */ |
| uint32_t blocksize; /* Size of one read/write blocks */ |
| uint32_t channel; /* 0: eMMC, 1: SDC */ |
| }; |
| |
| struct lc823450_partinfo_s |
| { |
| off_t startblock; |
| off_t nblocks; |
| }; |
| |
| /**************************************************************************** |
| * Private Data |
| ****************************************************************************/ |
| |
| static mutex_t g_lock = NXMUTEX_INITIALIZER; |
| static struct mtd_dev_s *g_mtdpart[LC823450_NPARTS]; |
| static struct mtd_dev_s *g_mtdmaster[CONFIG_MTD_DEV_MAX]; /* 0: eMMC, 1: SDC */ |
| |
| static const char g_mtdname[2][4] = |
| { |
| "sd", |
| "mmc" |
| }; |
| |
| static struct lc823450_partinfo_s partinfo[LC823450_NPARTS] = |
| { |
| { LC823450_PART1_START, LC823450_PART1_NBLOCKS, }, |
| { LC823450_PART2_START, LC823450_PART2_NBLOCKS, }, |
| { LC823450_PART3_START, LC823450_PART3_NBLOCKS, }, |
| { LC823450_PART4_START, LC823450_PART4_NBLOCKS, }, |
| { LC823450_PART5_START, LC823450_PART5_NBLOCKS, }, |
| { LC823450_PART6_START, LC823450_PART6_NBLOCKS, }, |
| { LC823450_PART7_START, LC823450_PART7_NBLOCKS, }, |
| { LC823450_PART8_START, LC823450_PART8_NBLOCKS, }, |
| { LC823450_PART9_START, LC823450_PART9_NBLOCKS, }, |
| { LC823450_PART10_START, LC823450_PART10_NBLOCKS, }, |
| }; |
| |
| /**************************************************************************** |
| * Private Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: lc823450_erase |
| * |
| * Description: |
| * Erase several blocks, each of the size previously reported. |
| * |
| ****************************************************************************/ |
| |
| static int lc823450_erase(struct mtd_dev_s *dev, off_t startblock, |
| size_t nblocks) |
| { |
| finfo("dev=%p startblock=%jd nblocks=%zd\n", |
| dev, (intmax_t)startblock, nblocks); |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: lc823450_bread |
| * |
| * Description: |
| * Read the specified number of blocks into the user provided buffer. |
| * |
| ****************************************************************************/ |
| |
| static ssize_t lc823450_bread(struct mtd_dev_s *dev, off_t startblock, |
| size_t nblocks, uint8_t *buf) |
| { |
| int ret; |
| struct lc823450_mtd_dev_s *priv = (struct lc823450_mtd_dev_s *)dev; |
| |
| unsigned long type; |
| |
| if (!((uint32_t)buf & 0x3)) |
| { |
| type = SDDR_RW_INC_WORD; |
| } |
| else if (!((uint32_t)buf & 0x1)) |
| { |
| type = SDDR_RW_INC_HWORD; |
| } |
| else |
| { |
| type = SDDR_RW_INC_BYTE; |
| } |
| |
| finfo("startblockr=%jd, nblocks=%zd buf=%p type=%lx\n", |
| (intmax_t)startblock, nblocks, buf, type); |
| |
| DEBUGASSERT(dev && buf); |
| |
| if (startblock >= priv->nblocks) |
| { |
| return -EINVAL; |
| } |
| |
| ret = nxmutex_lock(&priv->lock); |
| if (ret < 0) |
| { |
| return ret; |
| } |
| |
| if (!g_mtdmaster[priv->channel]) |
| { |
| finfo("device removed\n"); |
| nxmutex_unlock(&priv->lock); |
| return -ENODEV; |
| } |
| |
| if (startblock + nblocks > priv->nblocks) |
| { |
| nblocks = priv->nblocks - startblock; |
| } |
| |
| ret = lc823450_sdc_readsector(priv->channel, (unsigned long)(startblock), |
| (unsigned short)nblocks, buf, type); |
| nxmutex_unlock(&priv->lock); |
| |
| if (ret != OK) |
| { |
| finfo("ERROR: Failed to read sector, ret=%d startblock=%jd " |
| "nblocks=%zd\n", |
| ret, (intmax_t)startblock, nblocks); |
| return ret; |
| } |
| |
| return (ssize_t)nblocks; |
| } |
| |
| /**************************************************************************** |
| * Name: lc823450_bwrite |
| * |
| * Description: |
| * Write the specified number of blocks from the user provided buffer. |
| * |
| ****************************************************************************/ |
| |
| static ssize_t lc823450_bwrite(struct mtd_dev_s *dev, off_t startblock, |
| size_t nblocks, const uint8_t *buf) |
| { |
| int ret; |
| struct lc823450_mtd_dev_s *priv = (struct lc823450_mtd_dev_s *)dev; |
| |
| unsigned long type; |
| |
| if (!((uint32_t)buf & 0x3)) |
| { |
| type = SDDR_RW_INC_WORD; |
| } |
| else if (!((uint32_t)buf & 0x1)) |
| { |
| type = SDDR_RW_INC_HWORD; |
| } |
| else |
| { |
| type = SDDR_RW_INC_BYTE; |
| } |
| |
| finfo("startblockr=%jd, nblocks=%zd buf=%p type=%lx\n", |
| (intmax_t)startblock, nblocks, buf, type); |
| |
| DEBUGASSERT(dev && buf); |
| |
| if (startblock >= priv->nblocks) |
| { |
| return -EINVAL; |
| } |
| |
| ret = nxmutex_lock(&priv->lock); |
| if (ret < 0) |
| { |
| return ret; |
| } |
| |
| if (!g_mtdmaster[priv->channel]) |
| { |
| finfo("device removed\n"); |
| nxmutex_unlock(&priv->lock); |
| return -ENODEV; |
| } |
| |
| if (startblock + nblocks > priv->nblocks) |
| { |
| nblocks = priv->nblocks - startblock; |
| } |
| |
| ret = lc823450_sdc_writesector(priv->channel, (unsigned long)(startblock), |
| (unsigned short)nblocks, (void *)buf, type); |
| nxmutex_unlock(&priv->lock); |
| |
| if (ret != OK) |
| { |
| finfo("ERROR: Failed to write sector, ret=%d startblock=%jd " |
| "nblocks=%zd\n", |
| ret, (intmax_t)startblock, nblocks); |
| return ret; |
| } |
| |
| return (ssize_t)nblocks; |
| } |
| |
| /**************************************************************************** |
| * Name: lc823450_ioctl |
| ****************************************************************************/ |
| |
| static int lc823450_ioctl(struct mtd_dev_s *dev, int cmd, |
| unsigned long arg) |
| { |
| int ret; |
| struct lc823450_mtd_dev_s *priv = (struct lc823450_mtd_dev_s *)dev; |
| struct mtd_geometry_s *geo; |
| void **ppv; |
| |
| finfo("cmd=%xh, arg=%lxh\n", cmd, arg); |
| |
| ret = nxmutex_lock(&priv->lock); |
| if (ret < 0) |
| { |
| return ret; |
| } |
| |
| ret = -EINVAL; |
| |
| if (!g_mtdmaster[priv->channel]) |
| { |
| finfo("device removed\n"); |
| nxmutex_unlock(&priv->lock); |
| return -ENODEV; |
| } |
| |
| switch (cmd) |
| { |
| case MTDIOC_GEOMETRY: |
| finfo("MTDIOC_GEOMETRY\n"); |
| geo = (struct mtd_geometry_s *)arg; |
| if (geo) |
| { |
| memset(geo, 0, sizeof(*geo)); |
| |
| /* Populate the geometry structure with information needed to |
| * know the capacity and how to access the device. |
| */ |
| |
| geo->blocksize = priv->blocksize; |
| geo->erasesize = priv->blocksize; |
| geo->neraseblocks = priv->nblocks; |
| ret = OK; |
| } |
| |
| finfo("blocksize=%" PRId32 " erasesize=%" PRId32 |
| " neraseblocks=%" PRId32 "\n", geo->blocksize, |
| geo->erasesize, geo->neraseblocks); |
| break; |
| |
| case BIOC_PARTINFO: |
| { |
| struct partition_info_s *info = |
| (struct partition_info_s *)arg; |
| if (info != NULL) |
| { |
| info->numsectors = priv->nblocks; |
| info->sectorsize = priv->blocksize; |
| info->startsector = 0; |
| info->parent[0] = '\0'; |
| ret = OK; |
| } |
| } |
| break; |
| |
| case BIOC_XIPBASE: |
| finfo("BIOC_XIPBASE\n"); |
| ppv = (void**)arg; |
| if (ppv) |
| { |
| /* If media is directly acccesible, return (void*) base address |
| * of device memory. NULL otherwise. It is acceptable to omit |
| * this case altogether and simply return -ENOTTY. |
| */ |
| |
| *ppv = NULL; |
| ret = OK; |
| } |
| break; |
| |
| case MTDIOC_BULKERASE: |
| finfo("MTDIOC_BULKERASE\n"); |
| |
| /* Erase the entire device */ |
| |
| ret = OK; |
| break; |
| |
| #ifdef TODO |
| case MTDIOC_CIDSTR: |
| ret = lc823450_sdc_getcid(priv->channel, (char *)arg, 33); |
| break; |
| #endif |
| |
| default: |
| finfo("Command not found\n"); |
| ret = -ENOTTY; /* Bad command */ |
| break; |
| } |
| |
| nxmutex_unlock(&priv->lock); |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: mtd_mediainitialize |
| * |
| * Description: |
| * Detect media and initialize. |
| * |
| * Precondition: |
| * Semaphore has been taken. |
| ****************************************************************************/ |
| |
| static int mtd_mediainitialize(struct lc823450_mtd_dev_s *dev) |
| { |
| int ret; |
| unsigned long nblocks; |
| unsigned long blocksize; |
| uint32_t sysclk = lc823450_get_ahb(); |
| |
| finfo("enter\n"); |
| |
| ret = nxmutex_lock(&dev->lock); |
| if (ret < 0) |
| { |
| return ret; |
| } |
| |
| ret = lc823450_sdc_initialize(dev->channel); |
| DEBUGASSERT(ret == OK); |
| |
| ret = lc823450_sdc_setclock(dev->channel, 20000000, sysclk); |
| |
| if (ret != OK) |
| { |
| finfo("ERROR: Failed to set clock: ret=%d\n", ret); |
| goto exit_with_error; |
| } |
| |
| ret = lc823450_sdc_identifycard(dev->channel); |
| if (ret != OK) |
| { |
| finfo("ERROR: Failed to identify card: channel=%" PRId32 " ret=%d)\n", |
| dev->channel, ret); |
| goto exit_with_error; |
| } |
| |
| if (0 == dev->channel) |
| { |
| /* Try to change to High Speed DDR mode */ |
| |
| ret = lc823450_sdc_changespeedmode(dev->channel, 4); |
| finfo("ch=%" PRId32 " DDR mode ret=%d\n", dev->channel, ret); |
| } |
| else |
| { |
| #ifdef CONFIG_LC823450_SDC_UHS1 |
| /* Try to change to DDR50 mode */ |
| |
| ret = lc823450_sdc_changespeedmode(dev->channel, 4); |
| |
| if (0 == ret) |
| { |
| lldbg("ch=%d DDR50 mode ret=%d\n", dev->channel, ret); |
| goto get_card_size; |
| } |
| #endif |
| |
| /* Try to change to High Speed mode */ |
| |
| ret = lc823450_sdc_changespeedmode(dev->channel, 1); |
| |
| if (0 == ret) |
| { |
| ret = lc823450_sdc_setclock(dev->channel, 40000000, sysclk); |
| finfo("ch=%" PRId32 " HS mode ret=%d\n", dev->channel, ret); |
| } |
| } |
| |
| #ifdef CONFIG_LC823450_SDC_UHS1 |
| get_card_size: |
| #endif |
| |
| ret = lc823450_sdc_getcardsize(dev->channel, &nblocks, &blocksize); |
| if (ret != 0) |
| { |
| finfo("ERROR: No media found\n"); |
| goto exit_with_error; |
| } |
| |
| finfo("blocksize=%ld nblocks=%ld\n", blocksize, nblocks); |
| |
| dev->nblocks = nblocks; |
| dev->blocksize = blocksize; |
| |
| /* check if the media type is eMMC:1 */ |
| |
| if (1 == lc823450_sdc_refmediatype(dev->channel)) |
| { |
| /* cache on */ |
| |
| lc823450_sdc_cachectl(dev->channel, 1); |
| } |
| |
| finfo("ch=%" PRId32 " size=%" PRId64 "\n", |
| dev->channel, (uint64_t)blocksize * (uint64_t)nblocks); |
| |
| exit_with_error: |
| nxmutex_unlock(&dev->lock); |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: lc823450_mtd_allocdev |
| * |
| * Description: |
| * Allocate MTD device and initialize media. |
| * |
| * Precondition: |
| * Semaphore has been taken. |
| ****************************************************************************/ |
| |
| static struct mtd_dev_s *lc823450_mtd_allocdev(uint32_t channel) |
| { |
| int ret; |
| int mtype = lc823450_sdc_refmediatype(channel); |
| struct lc823450_mtd_dev_s *priv; |
| |
| /* Create an instance of the LC823450 MTD device state structure */ |
| |
| priv = (struct lc823450_mtd_dev_s *) |
| kmm_zalloc(sizeof(struct lc823450_mtd_dev_s)); |
| if (!priv) |
| { |
| finfo("Failed to allocate the LC823450 MTD devicestructure\n"); |
| return NULL; |
| } |
| |
| nxmutex_init(&priv->lock); |
| |
| priv->mtd.erase = lc823450_erase; |
| priv->mtd.bread = lc823450_bread; |
| priv->mtd.bwrite = lc823450_bwrite; |
| #ifdef CONFIG_MTD_BYTE_READ |
| priv->mtd.read = NULL; |
| #endif |
| #ifdef CONFIG_MTD_BYTE_WRITE |
| priv->mtd.write = NULL; |
| #endif |
| priv->mtd.ioctl = lc823450_ioctl; |
| priv->mtd.name = g_mtdname[mtype]; |
| |
| priv->channel = channel; |
| |
| ret = mtd_mediainitialize(priv); |
| if (ret != OK) |
| { |
| finfo("ERROR: Failed to initialize media\n"); |
| nxmutex_destroy(&priv->lock); |
| kmm_free(priv); |
| return NULL; |
| } |
| |
| /* Return the implementation-specific state structure as the MTD device */ |
| |
| return &priv->mtd; |
| } |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: lc823450_mtd_initialize |
| * |
| * Description: |
| * Create and initialize an MTD device instance. MTD devices are not |
| * registered in the file system, but are created as instances that can |
| * be bound to other functions (such as a block or character driver front |
| * end). |
| * |
| ****************************************************************************/ |
| |
| int lc823450_mtd_initialize(uint32_t devno) |
| { |
| int i; |
| int partno; |
| int ret; |
| struct lc823450_mtd_dev_s *priv; |
| off_t maxblock; |
| uint32_t ch = (devno == CONFIG_MTD_DEVNO_EMMC)? 0 : 1; |
| |
| #if CONFIG_MTD_DEV_MAX == 1 |
| DEBUGASSERT(devno == CONFIG_MTD_DEVNO_EMMC); |
| #else |
| DEBUGASSERT(devno == CONFIG_MTD_DEVNO_EMMC || |
| devno == CONFIG_MTD_DEVNO_SDC); |
| #endif |
| |
| /* Following block devices are created. |
| * |
| * /dev/mtdblock0 : Master partition |
| * /dev/mtdblock0p1 : 1st child partition |
| * /dev/mtdblock0p2 : 2nd child partition |
| * ... |
| * /dev/mtdblock0pN : Nth child partition |
| */ |
| |
| ret = nxmutex_lock(&g_lock); |
| if (ret < 0) |
| { |
| return ret; |
| } |
| |
| if (g_mtdmaster[ch]) |
| { |
| finfo("Device already registered\n"); |
| nxmutex_unlock(&g_lock); |
| return -EBUSY; |
| } |
| |
| /* Create master partition */ |
| |
| g_mtdmaster[ch] = lc823450_mtd_allocdev(ch); |
| if (!g_mtdmaster[ch]) |
| { |
| finfo("Failed to create master partition: ch=%" PRId32 "\n", ch); |
| nxmutex_unlock(&g_lock); |
| return -ENODEV; |
| } |
| |
| ret = mmcl_initialize(devno, g_mtdmaster[ch]); |
| if (ret != OK) |
| { |
| finfo("Failed to create block device on master partition: " |
| "ch=%" PRId32 "\n", |
| ch); |
| kmm_free(g_mtdmaster[ch]); |
| g_mtdmaster[ch] = NULL; |
| nxmutex_unlock(&g_lock); |
| return -ENODEV; |
| } |
| |
| #ifdef CONFIG_DEBUG |
| finfo("/dev/mtdblock%d created\n", devno); |
| #endif |
| |
| priv = (struct lc823450_mtd_dev_s *)g_mtdmaster[ch]; |
| |
| /* If SDC, create no child partition */ |
| |
| #if CONFIG_MTD_DEV_MAX > 1 |
| if (devno == CONFIG_MTD_DEVNO_SDC) |
| { |
| finfo("SDC has no child partitions.\n"); |
| nxmutex_unlock(&g_lock); |
| return OK; |
| } |
| #endif |
| |
| maxblock = priv->nblocks; |
| |
| /* Check partition table */ |
| |
| #ifdef CONFIG_DEBUG |
| for (i = 0; i < LC823450_NPARTS - 1; i++) |
| { |
| DEBUGASSERT(partinfo[i].startblock < partinfo[i + 1].startblock); |
| DEBUGASSERT(partinfo[i].startblock + partinfo[i].nblocks <= maxblock); |
| DEBUGASSERT(partinfo[i + 1].startblock + partinfo[i + 1].nblocks <= |
| maxblock); |
| } |
| #endif |
| |
| /* Create child partitions */ |
| |
| for (i = 0, partno = 1; partno <= LC823450_NPARTS; i++, partno++) |
| { |
| if (partno == LC823450_NPARTS) |
| { |
| if (partinfo[i].nblocks == 0) |
| { |
| partinfo[i].nblocks = (i == 0) ? |
| maxblock - partinfo[i].startblock : |
| maxblock - (partinfo[i - 1].startblock + |
| partinfo[i - 1].nblocks); |
| } |
| } |
| |
| g_mtdpart[i] = mtd_partition(g_mtdmaster[ch], partinfo[i].startblock, |
| partinfo[i].nblocks); |
| if (!g_mtdpart[i]) |
| { |
| finfo("%s(): mtd_partition failed. startblock=%" |
| PRIuOFF " nblocks=%" PRIuOFF "\n", __func__, |
| partinfo[i].startblock, partinfo[i].nblocks); |
| nxmutex_unlock(&g_lock); |
| DEBUGPANIC(); |
| return -EIO; |
| } |
| |
| ret = mmcl_createpartition(devno, partno, g_mtdpart[i]); |
| if (ret < 0) |
| { |
| finfo("%s(): mmcl_initialize part%d failed: %d\n", |
| __func__, partno, ret); |
| nxmutex_unlock(&g_lock); |
| DEBUGPANIC(); |
| return ret; |
| } |
| } |
| |
| nxmutex_unlock(&g_lock); |
| return OK; |
| } |
| |
| #if CONFIG_MTD_DEV_MAX > 1 |
| |
| /**************************************************************************** |
| * Name: lc823450_mtd_reinitialize_card |
| * |
| * Description: |
| * Called in resume sequence, if a card exists |
| * |
| ****************************************************************************/ |
| |
| int lc823450_mtd_reinitialize_card(void) |
| { |
| const uint32_t ch = 1; /* SDC */ |
| uint32_t sysclk = lc823450_get_ahb(); |
| |
| int ret = lc823450_sdc_clearcardinfo(ch); |
| |
| if (ret != OK) |
| { |
| finfo("ERROR: Failed to set clock: ret=%d\n", ret); |
| goto exit_with_error; |
| } |
| |
| ret = lc823450_sdc_setclock(ch, 20000000, sysclk); |
| |
| if (ret != OK) |
| { |
| finfo("ERROR: Failed to set clock: ret=%d\n", ret); |
| goto exit_with_error; |
| } |
| |
| ret = lc823450_sdc_identifycard(ch); |
| |
| if (ret != OK) |
| { |
| finfo("ERROR: Failed to identify card: ret=%d)\n", ret); |
| goto exit_with_error; |
| } |
| |
| #ifdef CONFIG_LC823450_SDC_UHS1 |
| /* Try to change to DDR50 mode */ |
| |
| ret = lc823450_sdc_changespeedmode(ch, 4); |
| |
| if (0 == ret) |
| { |
| goto exit_with_error; |
| } |
| #endif |
| |
| /* Try to change to High Speed mode */ |
| |
| ret = lc823450_sdc_changespeedmode(ch, 1); |
| |
| if (0 == ret) |
| { |
| ret = lc823450_sdc_setclock(ch, 40000000, sysclk); |
| } |
| |
| exit_with_error: |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: lc823450_mtd_uninitialize |
| ****************************************************************************/ |
| |
| int lc823450_mtd_uninitialize(uint32_t devno) |
| { |
| int ret; |
| char devname[16]; |
| struct lc823450_mtd_dev_s *priv; |
| const uint32_t ch = 1; /* SDC */ |
| finfo("devno=%" PRId32 "\n", devno); |
| |
| DEBUGASSERT(devno == CONFIG_MTD_DEVNO_SDC); |
| |
| ret = nxmutex_lock(&g_lock); |
| if (ret < 0) |
| { |
| return ret; |
| } |
| |
| priv = (struct lc823450_mtd_dev_s *)g_mtdmaster[ch]; |
| if (!priv) |
| { |
| finfo("SD card is not identified yet\n"); |
| nxmutex_unlock(&g_lock); |
| return -ENODEV; |
| } |
| |
| snprintf(devname, 16, "/dev/mtdblock%" PRId32, devno); |
| |
| #ifdef CONFIG_MTD_REGISTRATION |
| mtd_unregister(g_mtdmaster[ch]); |
| #endif |
| |
| ret = nxmutex_lock(&priv->lock); |
| if (ret < 0) |
| { |
| nxmutex_unlock(&g_lock); |
| return ret; |
| } |
| |
| ret = lc823450_sdc_clearcardinfo(ch); |
| DEBUGASSERT(ret == OK); |
| |
| nxmutex_unlock(&priv->lock); |
| |
| ret = mmcl_uninitialize(devname); |
| if (ret != OK) |
| { |
| finfo("mmcl_uninitialize failed: %d", ret); |
| } |
| |
| ret = lc823450_sdc_finalize(ch); |
| DEBUGASSERT(ret == OK); |
| |
| nxmutex_destroy(&priv->lock); |
| kmm_free(g_mtdmaster[ch]); |
| |
| g_mtdmaster[ch] = NULL; |
| |
| nxmutex_unlock(&g_lock); |
| |
| #ifdef CONFIG_DEBUG |
| finfo("/dev/mtdblock%d deleted\n", devno); |
| #endif |
| return OK; |
| } |
| #endif |