|  | /**************************************************************************** | 
|  | * drivers/mtd/ramtron.c | 
|  | * | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | * | 
|  | * 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. | 
|  | * | 
|  | ****************************************************************************/ | 
|  |  | 
|  | /* OPTIONS: | 
|  | *  - additional non-jedec standard device: FM25H20 | 
|  | *    must be enabled with the CONFIG_RAMTRON_FRAM_NON_JEDEC=y | 
|  | * | 
|  | * NOTE: | 
|  | *  - frequency is fixed to desired max by RAMTRON_INIT_CLK_MAX if new | 
|  | *    devices with different speed arrive, use the table to handle freq | 
|  | *    change and to fit all devices. Note that STM32_SPI driver is prone | 
|  | *    to too high freq. parameters and limit it within physical constraints. | 
|  | *    The speed may be changed through ioctl MTDIOC_SETSPEED | 
|  | * | 
|  | * TODO: | 
|  | *  - add support for sleep | 
|  | *  - add support for faster read FSTRD command | 
|  | */ | 
|  |  | 
|  | /**************************************************************************** | 
|  | * Included Files | 
|  | ****************************************************************************/ | 
|  |  | 
|  | #include <nuttx/config.h> | 
|  |  | 
|  | #include <sys/types.h> | 
|  | #include <inttypes.h> | 
|  | #include <stdint.h> | 
|  | #include <stdbool.h> | 
|  | #include <stdlib.h> | 
|  | #include <errno.h> | 
|  | #include <debug.h> | 
|  | #include <assert.h> | 
|  |  | 
|  | #include <nuttx/kmalloc.h> | 
|  | #include <nuttx/fs/ioctl.h> | 
|  | #include <nuttx/spi/spi.h> | 
|  | #include <nuttx/mtd/mtd.h> | 
|  |  | 
|  | /**************************************************************************** | 
|  | * Pre-processor Definitions | 
|  | ****************************************************************************/ | 
|  |  | 
|  | /* Used to abort the write wait */ | 
|  |  | 
|  | #ifndef CONFIG_MTD_RAMTRON_WRITEWAIT_COUNT | 
|  | #   define CONFIG_MTD_RAMTRON_WRITEWAIT_COUNT 100 | 
|  | #endif | 
|  |  | 
|  | /*  RAMTRON devices are flat! | 
|  | *  For purpose of the VFAT file system we emulate the following | 
|  | *  configuration: | 
|  | */ | 
|  |  | 
|  | #define RAMTRON_EMULATE_SECTOR_SHIFT  CONFIG_RAMTRON_EMULATE_SECTOR_SHIFT | 
|  | #define RAMTRON_EMULATE_PAGE_SHIFT    CONFIG_RAMTRON_EMULATE_PAGE_SHIFT | 
|  | #define RAMTRON_EMULATE_PAGE_SIZE     (1 << RAMTRON_EMULATE_PAGE_SHIFT) | 
|  |  | 
|  | /* RAMTRON Identification register values */ | 
|  |  | 
|  | #define RAMTRON_MANUFACTURER         0x7f | 
|  | #define RAMTRON_MEMORY_TYPE          0xc2 | 
|  |  | 
|  | /* Instructions: | 
|  | *      Command          Value       N Description         Addr Dummy Data | 
|  | */ | 
|  |  | 
|  | #define RAMTRON_WREN      0x06    /* 1 Write Enable          0   0     0 */ | 
|  | #define RAMTRON_WRDI      0x04    /* 1 Write Disable         0   0     0 */ | 
|  | #define RAMTRON_RDSR      0x05    /* 1 Read Status Register  0   0     >=1 */ | 
|  | #define RAMTRON_WRSR      0x01    /* 1 Write Status Register 0   0     1 */ | 
|  | #define RAMTRON_READ      0x03    /* 1 Read Data Bytes       A   0     >=1 */ | 
|  | #define RAMTRON_FSTRD     0x0b    /* 1 Higher speed read     A   1     >=1 */ | 
|  | #define RAMTRON_WRITE     0x02    /* 1 Write                 A   0     1-256 */ | 
|  | #define RAMTRON_SLEEP     0xb9    /* TODO: */ | 
|  | #define RAMTRON_RDID      0x9f    /* 1 Read Identification   0   0     1-3 */ | 
|  | #define RAMTRON_SN        0xc3    /* TODO: */ | 
|  |  | 
|  | /* Status register bit definitions */ | 
|  |  | 
|  | /*                                                            Bit 0: Res 0 */ | 
|  | #define RAMTRON_SR_WEL          (1 << 1)                   /* Bit 1: Write enable latch bit */ | 
|  | #define RAMTRON_SR_BP_SHIFT     (2)                        /* Bits 2-4: Block protect bits */ | 
|  | #define RAMTRON_SR_BP_MASK      (3 << RAMTRON_SR_BP_SHIFT) | 
|  | #define RAMTRON_SR_BP_NONE      (0 << RAMTRON_SR_BP_SHIFT) /* Unprotected */ | 
|  | #define RAMTRON_SR_BP_UPPERQTR  (1 << RAMTRON_SR_BP_SHIFT) /* Upper quarter */ | 
|  | #define RAMTRON_SR_BP_UPPERHALF (2 << RAMTRON_SR_BP_SHIFT) /* Upper half */ | 
|  | #define RAMTRON_SR_BP_ALL       (3 << RAMTRON_SR_BP_SHIFT) /* All sectors */ | 
|  | #define RAMTRON_SR_BP_SHIFT     (2)                        /* Bits 4-6: Reserved Always 0 */ | 
|  | #define RAMTRON_SR_WPEN         (1 << 7)                   /* Bit 7: Status register write protect */ | 
|  |  | 
|  | #define RAMTRON_DUMMY     0xa5 | 
|  |  | 
|  | /* Defines the initial speed compatible with all devices. In case of RAMTRON | 
|  | * the defined devices within the part list have all the same speed. | 
|  | */ | 
|  |  | 
|  | #define RAMTRON_INIT_CLK_MAX    40000000UL | 
|  |  | 
|  | /**************************************************************************** | 
|  | * Private Types | 
|  | ****************************************************************************/ | 
|  |  | 
|  | struct ramtron_parts_s | 
|  | { | 
|  | FAR const char *name; | 
|  | uint8_t id1; | 
|  | uint8_t id2; | 
|  | uint32_t size; | 
|  | uint8_t addr_len; | 
|  | uint32_t speed; | 
|  | #ifdef CONFIG_RAMTRON_CHUNKING | 
|  | bool chunked;                            /* True: write buffer size limitations */ | 
|  | uint16_t chunksize;                      /* Write chunk Size */ | 
|  | #endif | 
|  | }; | 
|  |  | 
|  | /* 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 ramtron_dev_s. | 
|  | */ | 
|  |  | 
|  | struct ramtron_dev_s | 
|  | { | 
|  | struct mtd_dev_s mtd;                    /* MTD interface */ | 
|  | FAR struct spi_dev_s *dev;               /* Saved SPI interface instance */ | 
|  | uint8_t sectorshift; | 
|  | uint8_t pageshift; | 
|  | uint16_t devid;                           /* SPI device ID to manage CS lines in board */ | 
|  | uint32_t nsectors; | 
|  | uint32_t npages; | 
|  | uint32_t speed;                          /* Overridable via ioctl */ | 
|  | FAR const struct ramtron_parts_s *part;  /* Part instance */ | 
|  | }; | 
|  |  | 
|  | /**************************************************************************** | 
|  | * Supported Part Lists | 
|  | ****************************************************************************/ | 
|  |  | 
|  | static const struct ramtron_parts_s g_ramtron_parts[] = | 
|  | { | 
|  | { | 
|  | "FM25V01",                    /* name */ | 
|  | 0x21,                         /* id1 */ | 
|  | 0x00,                         /* id2 */ | 
|  | 16L * 1024L,                  /* size */ | 
|  | 2,                            /* addr_len */ | 
|  | RAMTRON_INIT_CLK_MAX          /* speed */ | 
|  | #ifdef CONFIG_RAMTRON_CHUNKING | 
|  | , false,                      /* chunked */ | 
|  | RAMTRON_EMULATE_PAGE_SIZE     /* chunksize */ | 
|  | #endif | 
|  | }, | 
|  | { | 
|  | "FM25V01A",                   /* name */ | 
|  | 0x21,                         /* id1 */ | 
|  | 0x08,                         /* id2 */ | 
|  | 16L * 1024L,                  /* size */ | 
|  | 2,                            /* addr_len */ | 
|  | RAMTRON_INIT_CLK_MAX          /* speed */ | 
|  | #ifdef CONFIG_RAMTRON_CHUNKING | 
|  | , false,                      /* chunked */ | 
|  | RAMTRON_EMULATE_PAGE_SIZE     /* chunksize */ | 
|  | #endif | 
|  | }, | 
|  | { | 
|  | "FM25V02",                    /* name */ | 
|  | 0x22,                         /* id1 */ | 
|  | 0x00,                         /* id2 */ | 
|  | 32L * 1024L,                  /* size */ | 
|  | 2,                            /* addr_len */ | 
|  | RAMTRON_INIT_CLK_MAX          /* speed */ | 
|  | #ifdef CONFIG_RAMTRON_CHUNKING | 
|  | , false,                      /* chunked */ | 
|  | RAMTRON_EMULATE_PAGE_SIZE     /* chunksize */ | 
|  | #endif | 
|  | }, | 
|  | { | 
|  | "FM25V02A",                   /* name */ | 
|  | 0x22,                         /* id1 */ | 
|  | 0x08,                         /* id2 */ | 
|  | 32L * 1024L,                  /* size */ | 
|  | 2,                            /* addr_len */ | 
|  | RAMTRON_INIT_CLK_MAX          /* speed */ | 
|  | #ifdef CONFIG_RAMTRON_CHUNKING | 
|  | , false,                      /* chunked */ | 
|  | RAMTRON_EMULATE_PAGE_SIZE     /* chunksize */ | 
|  | #endif | 
|  | }, | 
|  | { | 
|  | "FM25V02A-DGQ",               /* name */ | 
|  | 0x22,                         /* id1 */ | 
|  | 0x48,                         /* id2 */ | 
|  | 32L * 1024L,                  /* size */ | 
|  | 2,                            /* addr_len */ | 
|  | RAMTRON_INIT_CLK_MAX          /* speed */ | 
|  | #ifdef CONFIG_RAMTRON_CHUNKING | 
|  | , false,                      /* chunked */ | 
|  | RAMTRON_EMULATE_PAGE_SIZE     /* chunksize */ | 
|  | #endif | 
|  | }, | 
|  | { | 
|  | "FM25VN02",                   /* name */ | 
|  | 0x22,                         /* id1 */ | 
|  | 0x01,                         /* id2 */ | 
|  | 32L * 1024L,                  /* size */ | 
|  | 2,                            /* addr_len */ | 
|  | RAMTRON_INIT_CLK_MAX          /* speed */ | 
|  | #ifdef CONFIG_RAMTRON_CHUNKING | 
|  | , false,                      /* chunked */ | 
|  | RAMTRON_EMULATE_PAGE_SIZE     /* chunksize */ | 
|  | #endif | 
|  | }, | 
|  | { | 
|  | "FM25V05",                    /* name */ | 
|  | 0x23,                         /* id1 */ | 
|  | 0x00,                         /* id2 */ | 
|  | 64L * 1024L,                  /* size */ | 
|  | 2,                            /* addr_len */ | 
|  | RAMTRON_INIT_CLK_MAX          /* speed */ | 
|  | #ifdef CONFIG_RAMTRON_CHUNKING | 
|  | , false,                      /* chunked */ | 
|  | RAMTRON_EMULATE_PAGE_SIZE     /* chunksize */ | 
|  | #endif | 
|  | }, | 
|  | { | 
|  | "FM25VN05",                   /* name */ | 
|  | 0x23,                         /* id1 */ | 
|  | 0x01,                         /* id2 */ | 
|  | 64L * 1024L,                  /* size */ | 
|  | 2,                            /* addr_len */ | 
|  | RAMTRON_INIT_CLK_MAX          /* speed */ | 
|  | #ifdef CONFIG_RAMTRON_CHUNKING | 
|  | , false,                      /* chunked */ | 
|  | RAMTRON_EMULATE_PAGE_SIZE     /* chunksize */ | 
|  | #endif | 
|  | }, | 
|  | { | 
|  | "FM25V10",                    /* name */ | 
|  | 0x24,                         /* id1 */ | 
|  | 0x00,                         /* id2 */ | 
|  | 128L * 1024L,                 /* size */ | 
|  | 3,                            /* addr_len */ | 
|  | RAMTRON_INIT_CLK_MAX          /* speed */ | 
|  | #ifdef CONFIG_RAMTRON_CHUNKING | 
|  | , false,                      /* chunked */ | 
|  | RAMTRON_EMULATE_PAGE_SIZE     /* chunksize */ | 
|  | #endif | 
|  | }, | 
|  | { | 
|  | "FM25VN10",                   /* name */ | 
|  | 0x24,                         /* id1 */ | 
|  | 0x01,                         /* id2 */ | 
|  | 128L * 1024L,                 /* size */ | 
|  | 3,                            /* addr_len */ | 
|  | RAMTRON_INIT_CLK_MAX          /* speed */ | 
|  | #ifdef CONFIG_RAMTRON_CHUNKING | 
|  | , false,                      /* chunked */ | 
|  | RAMTRON_EMULATE_PAGE_SIZE     /* chunksize */ | 
|  | #endif | 
|  | }, | 
|  | { | 
|  | "FM25V20A",                   /* name */ | 
|  | 0x25,                         /* id1 */ | 
|  | 0x08,                         /* id2 */ | 
|  | 256L * 1024L,                 /* size */ | 
|  | 3,                            /* addr_len */ | 
|  | RAMTRON_INIT_CLK_MAX          /* speed */ | 
|  | #ifdef CONFIG_RAMTRON_CHUNKING | 
|  | , false,                      /* chunked */ | 
|  | RAMTRON_EMULATE_PAGE_SIZE     /* chunksize */ | 
|  | #endif | 
|  | }, | 
|  | { | 
|  | "CY15B104Q",                  /* name */ | 
|  | 0x26,                         /* id1 */ | 
|  | 0x08,                         /* id2 */ | 
|  | 512L * 1024L,                 /* size */ | 
|  | 3,                            /* addr_len */ | 
|  | RAMTRON_INIT_CLK_MAX          /* speed */ | 
|  | #ifdef CONFIG_RAMTRON_CHUNKING | 
|  | , false,                      /* chunked */ | 
|  | RAMTRON_EMULATE_PAGE_SIZE     /* chunksize */ | 
|  | #endif | 
|  | }, | 
|  | { | 
|  | "MB85RS1MT",                  /* name */ | 
|  | 0x27,                         /* id1 */ | 
|  | 0x03,                         /* id2 */ | 
|  | 128L * 1024L,                 /* size */ | 
|  | 3,                            /* addr_len */ | 
|  | 25000000                      /* speed */ | 
|  | #ifdef CONFIG_RAMTRON_CHUNKING | 
|  | , false,                      /* chunked */ | 
|  | RAMTRON_EMULATE_PAGE_SIZE     /* chunksize */ | 
|  | #endif | 
|  | }, | 
|  | { | 
|  | "MB85RS2MT",                  /* name */ | 
|  | 0x48,                         /* id1 */ | 
|  | 0x03,                         /* id2 */ | 
|  | 256L * 1024L,                 /* size */ | 
|  | 3,                            /* addr_len */ | 
|  | 25000000                      /* speed */ | 
|  | #ifdef CONFIG_RAMTRON_CHUNKING | 
|  | , false,                      /* chunked */ | 
|  | RAMTRON_EMULATE_PAGE_SIZE     /* chunksize */ | 
|  | #endif | 
|  | }, | 
|  | { | 
|  | "MB85RS64V",                  /* name */ | 
|  | 0x03,                         /* id1 */ | 
|  | 0x02,                         /* id2 */ | 
|  | 8L * 1024L,                   /* size */ | 
|  | 2,                            /* addr_len */ | 
|  | 20000000                      /* speed */ | 
|  | #ifdef CONFIG_RAMTRON_CHUNKING | 
|  | , false,                      /* chunked */ | 
|  | RAMTRON_EMULATE_PAGE_SIZE     /* chunksize */ | 
|  | #endif | 
|  | }, | 
|  | { | 
|  | "MB85RS256B",                 /* name */ | 
|  | 0x05,                         /* id1 */ | 
|  | 0x09,                         /* id2 */ | 
|  | 32L * 1024L,                  /* size */ | 
|  | 2,                            /* addr_len */ | 
|  | 25000000                      /* speed */ | 
|  | #ifdef CONFIG_RAMTRON_CHUNKING | 
|  | , false,                      /* chunked */ | 
|  | RAMTRON_EMULATE_PAGE_SIZE     /* chunksize */ | 
|  | #endif | 
|  | }, | 
|  | #ifdef CONFIG_RAMTRON_CHUNKING | 
|  | { | 
|  | "MB85AS4MT",                  /* name */ | 
|  | 0xc9,                         /* id1 */ | 
|  | 0x03,                         /* id2 */ | 
|  | 512L * 1024L,                 /* size */ | 
|  | 3,                            /* addr_len */ | 
|  | RAMTRON_INIT_CLK_MAX,         /* speed */ | 
|  | true,                         /* chunked */ | 
|  | 256                           /* chunksize */ | 
|  | }, | 
|  | #endif | 
|  | #ifdef CONFIG_RAMTRON_FRAM_NON_JEDEC | 
|  | { | 
|  | "FM25H20",                    /* name */ | 
|  | 0xff,                         /* id1 */ | 
|  | 0xff,                         /* id2 */ | 
|  | 256L * 1024L,                 /* size */ | 
|  | 3,                            /* addr_len */ | 
|  | RAMTRON_INIT_CLK_MAX          /* speed */ | 
|  | #ifdef CONFIG_RAMTRON_CHUNKING | 
|  | , false,                      /* chunked */ | 
|  | RAMTRON_EMULATE_PAGE_SIZE     /* chunksize */ | 
|  | #endif | 
|  | }, | 
|  | #endif | 
|  | { | 
|  | NULL,                         /* name */ | 
|  | 0,                            /* id1 */ | 
|  | 0,                            /* id2 */ | 
|  | 0,                            /* size */ | 
|  | 0,                            /* addr_len */ | 
|  | 0                             /* speed */ | 
|  | #ifdef CONFIG_RAMTRON_CHUNKING | 
|  | , false,                      /* chunked */ | 
|  | 0,                            /* chunksize */ | 
|  | #endif | 
|  | } | 
|  | }; | 
|  |  | 
|  | /**************************************************************************** | 
|  | * Private Function Prototypes | 
|  | ****************************************************************************/ | 
|  |  | 
|  | /* Helpers */ | 
|  |  | 
|  | static void ramtron_lock(FAR struct ramtron_dev_s *priv); | 
|  | static inline void ramtron_unlock(FAR struct spi_dev_s *dev); | 
|  | static inline int ramtron_readid(struct ramtron_dev_s *priv); | 
|  | static void ramtron_writeenable(struct ramtron_dev_s *priv); | 
|  | static inline int ramtron_pagewrite(struct ramtron_dev_s *priv, | 
|  | FAR const uint8_t *buffer, | 
|  | off_t offset, | 
|  | size_t pagesize); | 
|  |  | 
|  | /* MTD driver methods */ | 
|  |  | 
|  | static int ramtron_erase(FAR struct mtd_dev_s *dev, | 
|  | off_t startblock, | 
|  | size_t nblocks); | 
|  | static ssize_t ramtron_bread(FAR struct mtd_dev_s *dev, | 
|  | off_t startblock, | 
|  | size_t nblocks, FAR uint8_t *buf); | 
|  | #ifdef CONFIG_RAMTRON_CHUNKING | 
|  | static ssize_t ramtron_bwrite_nonchunked(FAR struct mtd_dev_s *dev, | 
|  | off_t startblock, | 
|  | size_t nblocks, | 
|  | FAR const uint8_t *buffer); | 
|  | static ssize_t ramtron_bwrite_chunked(FAR struct mtd_dev_s *dev, | 
|  | off_t startblock, | 
|  | size_t nblocks, | 
|  | FAR const uint8_t *buf); | 
|  | #endif | 
|  | static ssize_t ramtron_bwrite(FAR struct mtd_dev_s *dev, | 
|  | off_t startblock, | 
|  | size_t nblocks, | 
|  | FAR const uint8_t *buf); | 
|  | static ssize_t ramtron_read(FAR struct mtd_dev_s *dev, | 
|  | off_t offset, | 
|  | size_t nbytes, | 
|  | FAR uint8_t *buffer); | 
|  | static int ramtron_ioctl(FAR struct mtd_dev_s *dev, | 
|  | int cmd, | 
|  | unsigned long arg); | 
|  |  | 
|  | /**************************************************************************** | 
|  | * Private Functions | 
|  | ****************************************************************************/ | 
|  |  | 
|  | /**************************************************************************** | 
|  | * Name: ramtron_lock | 
|  | ****************************************************************************/ | 
|  |  | 
|  | static void ramtron_lock(FAR struct ramtron_dev_s *priv) | 
|  | { | 
|  | FAR struct spi_dev_s *dev = priv->dev; | 
|  |  | 
|  | /* On SPI buses where there are multiple devices, it will be necessary to | 
|  | * lock SPI to have exclusive access to the buses for a sequence of | 
|  | * transfers.  The bus should be locked before the chip is selected. | 
|  | * | 
|  | * This is a blocking call and will not return until we have exclusive | 
|  | * access to the SPI bus. | 
|  | * We will retain that exclusive access until the bus is unlocked. | 
|  | */ | 
|  |  | 
|  | SPI_LOCK(dev, true); | 
|  |  | 
|  | /* After locking the SPI bus, the we also need call the setfrequency, | 
|  | * setbits, and setmode methods to make sure that the SPI is properly | 
|  | * configured for the device. | 
|  | * If the SPI bus is being shared, then it may have been left in an | 
|  | * incompatible state. | 
|  | */ | 
|  |  | 
|  | SPI_SETMODE(dev, SPIDEV_MODE3); | 
|  | SPI_SETBITS(dev, 8); | 
|  | SPI_HWFEATURES(dev, 0); | 
|  | SPI_SETFREQUENCY(dev, priv->speed); | 
|  | } | 
|  |  | 
|  | /**************************************************************************** | 
|  | * Name: ramtron_unlock | 
|  | ****************************************************************************/ | 
|  |  | 
|  | static inline void ramtron_unlock(FAR struct spi_dev_s *dev) | 
|  | { | 
|  | SPI_LOCK(dev, false); | 
|  | } | 
|  |  | 
|  | /**************************************************************************** | 
|  | * Name: ramtron_readid | 
|  | ****************************************************************************/ | 
|  |  | 
|  | static inline int ramtron_readid(struct ramtron_dev_s *priv) | 
|  | { | 
|  | uint16_t manufacturer; | 
|  | uint16_t memory; | 
|  | uint16_t capacity; | 
|  | uint16_t part; | 
|  | int i; | 
|  |  | 
|  | finfo("priv: %p\n", priv); | 
|  |  | 
|  | /* Lock the SPI bus, configure the bus, and select this FLASH part. */ | 
|  |  | 
|  | ramtron_lock(priv); | 
|  | SPI_SELECT(priv->dev, SPIDEV_FLASH(priv->devid), true); | 
|  |  | 
|  | /* Send the "Read ID (RDID)" command */ | 
|  |  | 
|  | SPI_SEND(priv->dev, RAMTRON_RDID); | 
|  |  | 
|  | /* Read the first six manufacturer ID bytes. */ | 
|  |  | 
|  | for (i = 0; i < 6; i++) | 
|  | { | 
|  | /* Read the next manufacturer byte */ | 
|  |  | 
|  | manufacturer = SPI_SEND(priv->dev, RAMTRON_DUMMY); | 
|  |  | 
|  | /* Fujitsu parts such as MB85RS1MT only have 1-byte for the | 
|  | * manufacturer ID.  The manufacturer code is "0x4". | 
|  | */ | 
|  |  | 
|  | if (i == 0 && manufacturer == 0x04) | 
|  | { | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | memory   = SPI_SEND(priv->dev, RAMTRON_DUMMY); | 
|  | capacity = SPI_SEND(priv->dev, RAMTRON_DUMMY);  /* fram.id1 */ | 
|  | part     = SPI_SEND(priv->dev, RAMTRON_DUMMY);  /* fram.id2 */ | 
|  |  | 
|  | /* Deselect the FLASH and unlock the bus */ | 
|  |  | 
|  | SPI_SELECT(priv->dev, SPIDEV_FLASH(priv->devid), false); | 
|  | ramtron_unlock(priv->dev); | 
|  |  | 
|  | /* Select part from the part list */ | 
|  |  | 
|  | for (priv->part = g_ramtron_parts; | 
|  | priv->part->name != NULL && | 
|  | !(priv->part->id1 == capacity && priv->part->id2 == part); | 
|  | priv->part++); | 
|  |  | 
|  | if (priv->part->name != NULL) | 
|  | { | 
|  | UNUSED(manufacturer); /* Eliminate warnings when debug is off */ | 
|  | UNUSED(memory);       /* Eliminate warnings when debug is off */ | 
|  |  | 
|  | finfo("RAMTRON %s of size %" PRIu32 " bytes (mf:%02" PRIx16 " mem:%02" | 
|  | PRIx16 " cap:%02" PRIx16 " part:%02" PRIx16 ")\n", | 
|  | priv->part->name, priv->part->size, manufacturer, memory, | 
|  | capacity, part); | 
|  |  | 
|  | priv->sectorshift = RAMTRON_EMULATE_SECTOR_SHIFT; | 
|  | priv->nsectors    = priv->part->size / | 
|  | (1 << RAMTRON_EMULATE_SECTOR_SHIFT); | 
|  | priv->pageshift   = RAMTRON_EMULATE_PAGE_SHIFT; | 
|  | priv->npages      = priv->part->size / | 
|  | (1 << RAMTRON_EMULATE_PAGE_SHIFT); | 
|  | priv->speed       = priv->part->speed; | 
|  | return OK; | 
|  | } | 
|  |  | 
|  | finfo("RAMTRON device not found\n"); | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | /**************************************************************************** | 
|  | * Name:  ramtron_writeenable | 
|  | ****************************************************************************/ | 
|  |  | 
|  | static void ramtron_writeenable(struct ramtron_dev_s *priv) | 
|  | { | 
|  | /* Select this FLASH part */ | 
|  |  | 
|  | SPI_SELECT(priv->dev, SPIDEV_FLASH(priv->devid), true); | 
|  |  | 
|  | /* Send "Write Enable (WREN)" command */ | 
|  |  | 
|  | SPI_SEND(priv->dev, RAMTRON_WREN); | 
|  |  | 
|  | /* Deselect the FLASH */ | 
|  |  | 
|  | SPI_SELECT(priv->dev, SPIDEV_FLASH(priv->devid), false); | 
|  | finfo("Enabled\n"); | 
|  | } | 
|  |  | 
|  | /**************************************************************************** | 
|  | * Name:  ramtron_sendaddr | 
|  | ****************************************************************************/ | 
|  |  | 
|  | static inline void ramtron_sendaddr(const struct ramtron_dev_s *priv, | 
|  | uint32_t addr) | 
|  | { | 
|  | DEBUGASSERT(priv->part->addr_len == 3 || priv->part->addr_len == 2); | 
|  |  | 
|  | if (priv->part->addr_len == 3) | 
|  | { | 
|  | SPI_SEND(priv->dev, (addr >> 16) & 0xff); | 
|  | } | 
|  |  | 
|  | SPI_SEND(priv->dev, (addr >> 8) & 0xff); | 
|  | SPI_SEND(priv->dev, addr & 0xff); | 
|  | } | 
|  |  | 
|  | /**************************************************************************** | 
|  | * Name:  ramtron_pagewrite | 
|  | ****************************************************************************/ | 
|  |  | 
|  | static inline int ramtron_pagewrite(struct ramtron_dev_s *priv, | 
|  | FAR const uint8_t *buffer, off_t page, | 
|  | size_t pagesize) | 
|  | { | 
|  | off_t offset = page * pagesize; | 
|  |  | 
|  | finfo("page: %08lx offset: %08lx\n", (long)page, (long)offset); | 
|  |  | 
|  | /* Enable the write access to the FLASH */ | 
|  |  | 
|  | ramtron_writeenable(priv); | 
|  |  | 
|  | /* Select this FLASH part */ | 
|  |  | 
|  | SPI_SELECT(priv->dev, SPIDEV_FLASH(priv->devid), true); | 
|  |  | 
|  | /* Send "Page Program (PP)" command */ | 
|  |  | 
|  | SPI_SEND(priv->dev, RAMTRON_WRITE); | 
|  |  | 
|  | /* Send the page offset high byte first. */ | 
|  |  | 
|  | ramtron_sendaddr(priv, offset); | 
|  |  | 
|  | /* Then write the specified number of bytes */ | 
|  |  | 
|  | SPI_SNDBLOCK(priv->dev, buffer, pagesize); | 
|  |  | 
|  | /* Deselect the FLASH: Chip Select high */ | 
|  |  | 
|  | SPI_SELECT(priv->dev, SPIDEV_FLASH(priv->devid), false); | 
|  | finfo("Written\n"); | 
|  |  | 
|  | return OK; | 
|  | } | 
|  |  | 
|  | /**************************************************************************** | 
|  | * Name: ramtron_erase | 
|  | ****************************************************************************/ | 
|  |  | 
|  | static int ramtron_erase(FAR struct mtd_dev_s *dev, | 
|  | off_t startblock, | 
|  | size_t nblocks) | 
|  | { | 
|  | finfo("startblock: %08lx nblocks: %d\n", | 
|  | (unsigned long)startblock, | 
|  | (int)nblocks); | 
|  | finfo("On RAMTRON devices erasing makes no sense, returning as OK\n"); | 
|  | return (int)nblocks; | 
|  | } | 
|  |  | 
|  | /**************************************************************************** | 
|  | * Name: ramtron_bread | 
|  | ****************************************************************************/ | 
|  |  | 
|  | static ssize_t ramtron_bread(FAR struct mtd_dev_s *dev, off_t startblock, | 
|  | size_t nblocks, FAR uint8_t *buffer) | 
|  | { | 
|  | FAR struct ramtron_dev_s *priv = (FAR struct ramtron_dev_s *)dev; | 
|  | ssize_t nbytes; | 
|  |  | 
|  | finfo("startblock: %08lx nblocks: %d\n", (long)startblock, (int)nblocks); | 
|  |  | 
|  | /* On this device, we can handle the block read just like the byte-oriented | 
|  | * read | 
|  | */ | 
|  |  | 
|  | nbytes = ramtron_read(dev, startblock << priv->pageshift, | 
|  | nblocks << priv->pageshift, buffer); | 
|  | if (nbytes > 0) | 
|  | { | 
|  | return nbytes >> priv->pageshift; | 
|  | } | 
|  |  | 
|  | return (int)nbytes; | 
|  | } | 
|  |  | 
|  | /**************************************************************************** | 
|  | * Name: ramtron_bwrite/ramtron_bwrite_nonchunked | 
|  | ****************************************************************************/ | 
|  |  | 
|  | #ifdef CONFIG_RAMTRON_CHUNKING | 
|  | static ssize_t ramtron_bwrite_nonchunked(FAR struct mtd_dev_s *dev, | 
|  | off_t startblock, | 
|  | size_t nblocks, | 
|  | FAR const uint8_t *buffer) | 
|  | #else | 
|  | static ssize_t ramtron_bwrite(FAR struct mtd_dev_s *dev, | 
|  | off_t startblock, | 
|  | size_t nblocks, | 
|  | FAR const uint8_t *buffer) | 
|  | #endif | 
|  | { | 
|  | FAR struct ramtron_dev_s *priv = (FAR struct ramtron_dev_s *)dev; | 
|  | size_t blocksleft = nblocks; | 
|  |  | 
|  | finfo("startblock: %08lx nblocks: %d\n", (long)startblock, (int)nblocks); | 
|  | DEBUGASSERT(priv != NULL && buffer != NULL); | 
|  |  | 
|  | /* Lock the SPI bus and write each page to FLASH */ | 
|  |  | 
|  | ramtron_lock(priv); | 
|  | while (blocksleft-- > 0) | 
|  | { | 
|  | if (ramtron_pagewrite(priv, buffer, startblock, 1 << priv->pageshift)) | 
|  | { | 
|  | nblocks = 0; | 
|  | break; | 
|  | } | 
|  |  | 
|  | startblock++; | 
|  | } | 
|  |  | 
|  | ramtron_unlock(priv->dev); | 
|  | return nblocks; | 
|  | } | 
|  |  | 
|  | /**************************************************************************** | 
|  | * Name: ramtron_bwrite_chunked | 
|  | ****************************************************************************/ | 
|  |  | 
|  | #ifdef CONFIG_RAMTRON_CHUNKING | 
|  | static ssize_t ramtron_bwrite_chunked(FAR struct mtd_dev_s *dev, | 
|  | off_t startblock, | 
|  | size_t nblocks, | 
|  | FAR const uint8_t *buffer) | 
|  | { | 
|  | FAR struct ramtron_dev_s *priv = (FAR struct ramtron_dev_s *)dev; | 
|  | FAR const struct ramtron_parts_s *part; | 
|  | size_t blocksleft = nblocks; | 
|  | uint32_t p; | 
|  | uint32_t writesplits; | 
|  | off_t newstartblock; | 
|  |  | 
|  | finfo("startblock: %08lx nblocks: %d\n", (long)startblock, (int)nblocks); | 
|  |  | 
|  | DEBUGASSERT(priv != NULL && priv->part != NULL && buffer != NULL); | 
|  | part = priv->part; | 
|  |  | 
|  | writesplits   = (1 << priv->pageshift) / part->chunksize; | 
|  | newstartblock = startblock * writesplits; | 
|  |  | 
|  | /* Lock the SPI bus and write each page to FLASH */ | 
|  |  | 
|  | ramtron_lock(priv); | 
|  | while (blocksleft-- > 0) | 
|  | { | 
|  | /* Split writes in chunksize chunks */ | 
|  |  | 
|  | for (p = 0; p < writesplits; p++) | 
|  | { | 
|  | if (ramtron_pagewrite(priv, | 
|  | buffer + p * part->chunksize, | 
|  | newstartblock, | 
|  | part->chunksize)) | 
|  | { | 
|  | nblocks = 0; | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | newstartblock++; | 
|  | } | 
|  | } | 
|  |  | 
|  | out: | 
|  | ramtron_unlock(priv->dev); | 
|  | return nblocks; | 
|  | } | 
|  | #endif | 
|  |  | 
|  | /**************************************************************************** | 
|  | * Name: ramtron_bwrite | 
|  | ****************************************************************************/ | 
|  |  | 
|  | #ifdef CONFIG_RAMTRON_CHUNKING | 
|  | static ssize_t ramtron_bwrite(FAR struct mtd_dev_s *dev, off_t startblock, | 
|  | size_t nblocks, FAR const uint8_t *buffer) | 
|  | { | 
|  | FAR struct ramtron_dev_s *priv = (FAR struct ramtron_dev_s *)dev; | 
|  | FAR const struct ramtron_parts_s *part; | 
|  |  | 
|  | DEBUGASSERT(priv != NULL && priv->part != NULL && buffer != NULL); | 
|  | part = priv->part; | 
|  |  | 
|  | /* Handle parts that require chunked output differently */ | 
|  |  | 
|  | if (part->chunked) | 
|  | { | 
|  | return ramtron_bwrite_chunked(dev, startblock, nblocks, buffer); | 
|  | } | 
|  | else | 
|  | { | 
|  | return ramtron_bwrite_nonchunked(dev, startblock, nblocks, buffer); | 
|  | } | 
|  | } | 
|  | #endif | 
|  |  | 
|  | /**************************************************************************** | 
|  | * Name: ramtron_read | 
|  | ****************************************************************************/ | 
|  |  | 
|  | static ssize_t ramtron_read(FAR struct mtd_dev_s *dev, | 
|  | off_t offset, | 
|  | size_t nbytes, | 
|  | FAR uint8_t *buffer) | 
|  | { | 
|  | FAR struct ramtron_dev_s *priv = (FAR struct ramtron_dev_s *)dev; | 
|  |  | 
|  | finfo("offset: %08lx nbytes: %d\n", (long)offset, (int)nbytes); | 
|  |  | 
|  | /* Lock the SPI bus NOW because the ramtron_waitwritecomplete call must be | 
|  | * executed with the bus locked. | 
|  | */ | 
|  |  | 
|  | ramtron_lock(priv); | 
|  |  | 
|  | /* Select this FLASH part */ | 
|  |  | 
|  | SPI_SELECT(priv->dev, SPIDEV_FLASH(priv->devid), true); | 
|  |  | 
|  | /* Send "Read from Memory " instruction */ | 
|  |  | 
|  | SPI_SEND(priv->dev, RAMTRON_READ); | 
|  |  | 
|  | /* Send the page offset high byte first. */ | 
|  |  | 
|  | ramtron_sendaddr(priv, offset); | 
|  |  | 
|  | /* Then read all of the requested bytes */ | 
|  |  | 
|  | SPI_RECVBLOCK(priv->dev, buffer, nbytes); | 
|  |  | 
|  | /* Deselect the FLASH and unlock the SPI bus */ | 
|  |  | 
|  | SPI_SELECT(priv->dev, SPIDEV_FLASH(priv->devid), false); | 
|  | ramtron_unlock(priv->dev); | 
|  |  | 
|  | finfo("return nbytes: %d\n", (int)nbytes); | 
|  | return nbytes; | 
|  | } | 
|  |  | 
|  | /**************************************************************************** | 
|  | * Name: ramtron_ioctl | 
|  | ****************************************************************************/ | 
|  |  | 
|  | static int ramtron_ioctl(FAR struct mtd_dev_s *dev, | 
|  | int cmd, | 
|  | unsigned long arg) | 
|  | { | 
|  | FAR struct ramtron_dev_s *priv = (FAR struct ramtron_dev_s *)dev; | 
|  | int ret = -EINVAL; /* Assume good command with bad parameters */ | 
|  |  | 
|  | finfo("cmd: %d\n", cmd); | 
|  |  | 
|  | switch (cmd) | 
|  | { | 
|  | case MTDIOC_GEOMETRY: | 
|  | { | 
|  | FAR struct mtd_geometry_s *geo = | 
|  | (FAR struct mtd_geometry_s *)((uintptr_t)arg); | 
|  | if (geo) | 
|  | { | 
|  | memset(geo, 0, sizeof(*geo)); | 
|  |  | 
|  | /* Populate the geometry structure with information need to | 
|  | * know the capacity and how to access the device. | 
|  | * | 
|  | * NOTE: | 
|  | * that the device is treated as though it where just an array | 
|  | * of fixed size blocks.  That is most likely not true, but the | 
|  | * client will expect the device logic to do whatever is | 
|  | * necessary to make it appear so. | 
|  | */ | 
|  |  | 
|  | geo->blocksize    = (1 << priv->pageshift); | 
|  | geo->erasesize    = (1 << priv->sectorshift); | 
|  | geo->neraseblocks = priv->nsectors; | 
|  | ret               = OK; | 
|  |  | 
|  | finfo("blocksize: %ld erasesize: %ld neraseblocks: %ld\n", | 
|  | geo->blocksize, geo->erasesize, geo->neraseblocks); | 
|  | } | 
|  | } | 
|  | break; | 
|  |  | 
|  | case BIOC_PARTINFO: | 
|  | { | 
|  | FAR struct partition_info_s *info = | 
|  | (FAR struct partition_info_s *)arg; | 
|  | if (info != NULL) | 
|  | { | 
|  | info->numsectors  = priv->nsectors * | 
|  | (priv->sectorshift - priv->pageshift); | 
|  | info->sectorsize  = 1 << priv->pageshift; | 
|  | info->startsector = 0; | 
|  | info->parent[0]   = '\0'; | 
|  | ret               = OK; | 
|  | } | 
|  | } | 
|  | break; | 
|  |  | 
|  | case MTDIOC_BULKERASE: | 
|  | finfo("BULDERASE: Makes no sense in ramtron.\n"); | 
|  | finfo("BULDERASE: Let's confirm operation as OK\n"); | 
|  | ret = OK; | 
|  | break; | 
|  |  | 
|  | #ifdef CONFIG_RAMTRON_SETSPEED | 
|  | case MTDIOC_SETSPEED: | 
|  | { | 
|  | if (arg > 0 && arg <= RAMTRON_INIT_CLK_MAX) | 
|  | { | 
|  | priv->speed = arg; | 
|  | finfo("set bus speed to %lu\n", priv->speed); | 
|  | ret = OK; | 
|  | } | 
|  | } | 
|  | break; | 
|  | #endif | 
|  |  | 
|  | default: | 
|  | ret = -ENOTTY; /* Bad command */ | 
|  | break; | 
|  | } | 
|  |  | 
|  | finfo("return %d\n", ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /**************************************************************************** | 
|  | * Public Functions | 
|  | ****************************************************************************/ | 
|  |  | 
|  | /**************************************************************************** | 
|  | * Name: ramtron_initialize | 
|  | * | 
|  | * Description: | 
|  | *   Create an initialize 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). | 
|  | * | 
|  | * Note: | 
|  | *   This implementation can handle multiple FLASH parts per SPI device by | 
|  | *   using the spi_devid to handle chip select between devices. The spi_devid | 
|  | *   passed to this function must be handled by the corresponding spi select | 
|  | *   function in board files. | 
|  | ****************************************************************************/ | 
|  |  | 
|  | FAR struct mtd_dev_s *ramtron_initialize(FAR struct spi_dev_s *dev, | 
|  | uint16_t spi_devid) | 
|  | { | 
|  | FAR struct ramtron_dev_s *priv; | 
|  |  | 
|  | finfo("dev: %p\n", dev); | 
|  |  | 
|  | /* Allocate a state structure (we allocate the structure instead of using | 
|  | * a fixed, static allocation so that we can handle multiple FLASH devices. | 
|  | */ | 
|  |  | 
|  | priv = (FAR struct ramtron_dev_s *) | 
|  | kmm_zalloc(sizeof(struct ramtron_dev_s)); | 
|  | if (priv) | 
|  | { | 
|  | /* Initialize the allocated structure. (unsupported methods were | 
|  | * nullified by kmm_zalloc). | 
|  | */ | 
|  |  | 
|  | priv->mtd.erase  = ramtron_erase; | 
|  | priv->mtd.bread  = ramtron_bread; | 
|  | priv->mtd.bwrite = ramtron_bwrite; | 
|  | priv->mtd.read   = ramtron_read; | 
|  | priv->mtd.ioctl  = ramtron_ioctl; | 
|  | priv->mtd.name   = "ramtron"; | 
|  | priv->dev        = dev; | 
|  | priv->devid      = spi_devid; | 
|  |  | 
|  | /* Deselect the FLASH */ | 
|  |  | 
|  | SPI_SELECT(dev, SPIDEV_FLASH(priv->devid), false); | 
|  |  | 
|  | /* Identify the FLASH chip and get its capacity */ | 
|  |  | 
|  | if (ramtron_readid(priv) != OK) | 
|  | { | 
|  | /* Unrecognized! Discard all of that work we just did and | 
|  | * return NULL | 
|  | */ | 
|  |  | 
|  | kmm_free(priv); | 
|  | return NULL; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Return the implementation-specific state structure as the MTD device */ | 
|  |  | 
|  | finfo("Return %p\n", priv); | 
|  | return (FAR struct mtd_dev_s *)priv; | 
|  | } |