[PATCH] mtd: add driver for the flash in Lattice machxo2 FPGAs
Varka Bhadram
varkabhadram at gmail.com
Thu Jul 24 21:11:25 PDT 2014
On 07/24/2014 09:52 PM, Martin Fuzzey wrote:
> This driver provides two MTD devices, one for the configuration data
> (FPGA bitstream) and another for the general purpose user flash.
>
> Both behave as normal MTD devices bit the configuration one has
> some extra sysfs entries to synchronize updating.
>
> Signed-off-by: Martin Fuzzey <mfuzzey at parkeon.com>
> ---
> .../bindings/mtd/lattice-machxo2-fpga.txt | 32 +
> Documentation/mtd/machxo2-fpga.txt | 104 ++
> drivers/mtd/devices/Kconfig | 31 +
> drivers/mtd/devices/Makefile | 3
> drivers/mtd/devices/machxo2-core.c | 929 ++++++++++++++++++++
> drivers/mtd/devices/machxo2-spi.c | 114 ++
> drivers/mtd/devices/machxo2.h | 24 +
> 7 files changed, 1236 insertions(+), 1 deletion(-)
> create mode 100644 Documentation/devicetree/bindings/mtd/lattice-machxo2-fpga.txt
> create mode 100644 Documentation/mtd/machxo2-fpga.txt
> create mode 100644 drivers/mtd/devices/machxo2-core.c
> create mode 100644 drivers/mtd/devices/machxo2-spi.c
> create mode 100644 drivers/mtd/devices/machxo2.h
>
> diff --git a/Documentation/devicetree/bindings/mtd/lattice-machxo2-fpga.txt b/Documentation/devicetree/bindings/mtd/lattice-machxo2-fpga.txt
> new file mode 100644
> index 0000000..2fd30fa
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/mtd/lattice-machxo2-fpga.txt
> @@ -0,0 +1,32 @@
> +MTD SPI driver for the configuration (bitstream) and user flash of the Lattice
> +Machxo2 family FPGAs.
> +
> +Required properties:
> +- compatible : lattice,machxo2
> +
> +The two flash regions of the device are represented by the child nodes:
> + "configflash" : for the configuration data (FPGA bitstream)
> + "userflash" : for the general purpose flash memory
> +
> +Both of these child nodes are optional (if the child node is missing no
> +mtd device will be created)
> +
> +Each of these child nodes have the same optional properties:
> +- linux,mtd-name : name of mtd to create
> +
> +
> +Example:
> +&ecspi2 {
> + mainboard-fpga at 2 {
> + compatible = "lattice,machxo2";
> + spi-max-frequency = <25000000>;
> + reg = <2>;
> + configflash {
> + linux,mtd-name = "mainboard-fpga-bitstream";
> + };
> + userflash {
> + linux,mtd-name = "mainboard-fpga-userflash";
> + };
> + };
> +};
> +
> diff --git a/Documentation/mtd/machxo2-fpga.txt b/Documentation/mtd/machxo2-fpga.txt
> new file mode 100644
> index 0000000..c7727a7
> --- /dev/null
> +++ b/Documentation/mtd/machxo2-fpga.txt
> @@ -0,0 +1,104 @@
> +The Lattice MachXO2 family of FPGAs contain varying amounts of flash memory.
> +
> +The memory is divided into two parts:
> +
> +* Configuration flash :
> + Stores the data that determines the logic implemented by the FPGA
> + (sometimes called a "bitstream").
> + This data is created using the Lattice design tools.
> +
> +* User flash :
> + General user defined data like any other flash memory.
> +
> +The amount of each type of memory available is hardware defined and depends
> +on the exact chip model.
> +
> +Each part consists of a single erase block, meaning that the user flash is
> +probably not suited as backing store for a filesystem.
> +
> +The FPGA logic actually "executes" from internal RAM, intialised from the
> +configuration flash at power up. This means that it is possible to erase and
> +reprogram the configuration flash without affecting the FPGA's function.
> +
> +In order to access these memories the FPGA is connected to the CPU over a
> +SPI or I2C bus (the driver currently only supports SPI).
> +This is in addition to any other connection the FPGA may have to the CPU bus
> +as part of the functionality of it's programmed design.
> +
> +The user flash is exposed as a standard MTD device.
> +
> +=== Sysfs Interface ===
> +The configuration flash is also exposed as a standard MTD but there are also
> +a few special sysfs entries used to identify the FPGA and control the update
> +procedure.
> +
> +device_id (ro)
> + 4 byte hexadecimal raw device model identifer
> + Eg: 01 2b bo 43
> +
> +name (ro)
> + Human readable device type name (determined from device_id)
> + Eg: MachXO2-2000 / MachXO2-1200U (those two devices have the same id)
> +
> +trace_id (ro)
> + 8 byte hexadecimal unique chip identifer
> + Eg: 55 44 30 80 78 0c 68 4e
> +
> +usercode (ro)
> + 4 byte hexadecimal user defined data.
> + This is programmed into the device as part of the bitstream and
> + may be used for purposes such as version identification etc.
> + Eg: 00 01 01 02
> +
> +running (ro)
> + Boolean flag indicating if the internal RAM has been loaded from
> + the flash and the configuration data accepted.
> +
> +prog_done (rw)
> + Boolean flag indicating if the configuration flash memory has been
> + programmed.
> + Erasing the configuration flash will set this to zero.
> + Writing any value will set it to one
> + The FPGA cannot apply the data until this is set
> +
> +refresh (wo)
> + Writing any value causes the FPGA to refresh it's RAM from the flash
> + and apply the new configuration.
> +
> +
> +=== Bitstream update example ===
> +1) Erase the configuration flash
> + # flash_erase /dev/mtd2 0 0
> + Erasing 49 Kibyte @ 0 -- 100 % complete
> +
> + # cat /sys/bus/spi/devices/spi2.2/prog_done
> + 0
> +
> + The FPGA is still running normally from RAM
> + # cat /sys/bus/spi/devices/spi2.2/usercode
> + 00 01 01 00
> +
> +2) Write the new bitstram
> + # cp bitsteam.bin /dev/mtd2
> +
> +3) Signal that we have finished writing
> + # echo 1 > /sys/bus/spi/devices/spi2.2/prog_done
> +
> +4) Request a refresh
> + # echo 1 > /sys/bus/spi/devices/spi2.2/refresh
> +
> +5) Observe new usercode
> + # cat /sys/bus/spi/devices/spi2.2/usercode
> + 00 01 01 02
> +
> +Note that step 4) is the critical one as it will cause any "hardware"
> +implemented in the FPGA to temporarilly disappear. Therefore if the FPGA is
> +being used to implement "hardware" devices visible to linux the appropriate
> +drivers should be unbound between steps 3 and 4.
> +
> +Alternatively, step 4 may be omitted and a full power cycle done instead.
> +
> +In some cases a reboot may be done instead of a power cycle but that will
> +depend on the board and bootloader design. It will only work if the bootloader
> +requests a refresh using the PROGN electrical signal to the FPGA or sending
> +the refresh command over SPI.
> diff --git a/drivers/mtd/devices/Kconfig b/drivers/mtd/devices/Kconfig
> index c49d0b1..7ecd457 100644
> --- a/drivers/mtd/devices/Kconfig
> +++ b/drivers/mtd/devices/Kconfig
> @@ -120,6 +120,37 @@ config MTD_BCM47XXSFLASH
> registered by bcma as platform devices. This enables driver for
> serial flash memories (only read-only mode is implemented).
>
> +config MTD_MACHXO2_CORE
> + tristate "Lattice MACHXO2 family FPGAs"
> + depends on SPI_MASTER
> + help
> + This provides access to the configuration ( bitstream) and
> + general purpose flash memories of the Lattice MACHXO2 family FPGAs
> + See Documenation/mtd/machxo2-fpga for usage information
> +
> + You should also select a bus interface below (currently only
> + SPI is implemented but the hardware supports I2C too)
> +
> +config MTD_MACHXO2_CONFIGFLASH
> + boolean "Allow access to MACHXO2 configuration flash"
> + depends on MTD_MACHXO2_CORE
> + help
> + If selected the configuration flash (bitstream) in the FPGA
> + will be exposed as a linux MTD device.
> +
> +config MTD_MACHXO2_USERFLASH
> + boolean "Allow access to MACHXO2 user flash"
> + depends on MTD_MACHXO2_CORE
> + help
> + If selected the general purpose user flash in the FPGA will
> + be exposed as a linux MTD device.
> +
> +config MTD_MACHXO2_SPI
> + tristate "Lattice MACHXO2 family FPGAs using SPI bus interface"
> + depends on MTD_MACHXO2_CORE && SPI_MASTER
> + help
> + Select this if your MAXCHXO2 FPGA is connected via a SPI bus.
> +
> config MTD_SLRAM
> tristate "Uncached system RAM"
> help
> diff --git a/drivers/mtd/devices/Makefile b/drivers/mtd/devices/Makefile
> index c68868f..f39df4a 100644
> --- a/drivers/mtd/devices/Makefile
> +++ b/drivers/mtd/devices/Makefile
> @@ -17,6 +17,7 @@ obj-$(CONFIG_MTD_SPEAR_SMI) += spear_smi.o
> obj-$(CONFIG_MTD_SST25L) += sst25l.o
> obj-$(CONFIG_MTD_BCM47XXSFLASH) += bcm47xxsflash.o
> obj-$(CONFIG_MTD_ST_SPI_FSM) += st_spi_fsm.o
> -
> +obj-$(CONFIG_MTD_MACHXO2_CORE) += machxo2-core.o
> +obj-$(CONFIG_MTD_MACHXO2_SPI) += machxo2-spi.o
>
> CFLAGS_docg3.o += -I$(src)
> diff --git a/drivers/mtd/devices/machxo2-core.c b/drivers/mtd/devices/machxo2-core.c
> new file mode 100644
> index 0000000..07f4bf1
> --- /dev/null
> +++ b/drivers/mtd/devices/machxo2-core.c
> @@ -0,0 +1,929 @@
> +/*
> + * Core MTD driver for Lattice MachXO2 family FPGAs
> + *
> + * It supplies 2 mtd devices for accessing:
> + * Configuration flash (FPGA bitstream)
> + * Userflash (general user data)
> + *
> + * In addition it provides a number of sysfs attributes to identify the
> + * FPGA and control the programming process.
> + * See Documentation/mtd/machxo2-fpga.txt
> + *
> + * Copyright 2014 Parkeon
> + * Martin Fuzzey <mfuzzey at parkeon.com>
> + *
> + * This program is free software; you can redistribute it and/or modify it under
> + * the terms of the GNU General Public License version 2 as published by the
> + * Free Software Foundation.
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/slab.h>
> +#include <linux/module.h>
> +#include <linux/mtd/mtd.h>
> +#include <linux/mutex.h>
> +#include <linux/of.h>
Includes in alphabetical order...
> +
> +#include "machxo2.h"
> +
> +#define MACHXO2_PAGESIZE 16
> +#define MACHXO2_DEVICE_ID_LEN 4
> +#define MACHXO2_TRACE_ID_LEN 8
> +#define MACHXO2_USERCODE_LEN 4
> +
> +#define MACHXO2_CMD_SET_ADDR 0xb4
> +#define MACHXO2_CMD_READ_CONFIG_FLASH 0x73
> +#define MACHXO2_CMD_WRITE_CONFIG_FLASH 0x70
> +#define MACHXO2_CMD_READ_UFM 0xca
> +#define MACHXO2_CMD_WRITE_UFM 0xc9
> +#define MACHXO2_CMD_ERASE 0x0e
> +#define MACHXO2_CMD_READ_TRACE_ID {0x19, 0x00, 0x00, 0x00}
> +#define MACHXO2_CMD_READ_USERCODE {0xc0, 0x00, 0x00, 0x00}
> +#define MACHXO2_CMD_READ_DEVICE_ID {0xe0, 0x00, 0x00, 0x00}
> +#define MACHXO2_CMD_BYPASS {0xff, 0xff, 0xff, 0xff}
> +#define MACHXO2_CMD_ENABLE_CONFIG_INTERFACE {0x74, 0x08, 0x00, 0x00}
> +#define MACHXO2_CMD_DISABLE_CONFIG_INTERFACE {0x26, 0x00, 0x00}
> +#define MACHXO2_CMD_PROGRAM_DONE {0x5e, 0x00, 0x00, 0x00}
> +#define MACHXO2_CMD_REFRESH {0x79, 0x00, 0x00}
> +#define MACHXO2_CMD_READ_STATUS_REGSITER {0x3c, 0x00, 0x00, 0x00}
> +#define MACHXO2_STATUS_DONE (1 << 8)
> +#define MACHXO2_STATUS_BUSY (1 << 12)
> +#define MACHXO2_STATUS_FAILED (1 << 13)
> +#define MACHXO2_STATUS_ERR_SHIFT 23
> +#define MACHXO2_STATUS_ERR_MASK (7 << MACHXO2_STATUS_ERR_SHIFT)
> +
> +struct machxo2_flash_descriptor {
> + const char *name;
> + uint8_t read_cmd;
> + uint8_t write_cmd;
> + uint8_t set_addr_param;
> + uint8_t erase_param;
> +};
> +
> +struct machxo2_flash {
> + const char *name;
> + uint32_t nr_pages;
> + const struct machxo2_flash_descriptor *descr;
> + struct machxo2 *machxo2;
> + struct mtd_info mtd;
> +};
> +
> +struct machxo2_device_info {
> + uint8_t b2;
> + const char *name;
> + uint32_t cfg_pages;
> + uint32_t ufm_pages;
> + int max_erase_ms;
> +};
> +
> +struct machxo2 {
> + struct device *dev;
> + struct mutex lock;
> + struct machxo2_busops *busops;
> + uint8_t device_id[MACHXO2_DEVICE_ID_LEN];
> + const struct machxo2_device_info *device_info;
> + struct machxo2_flash configflash;
> + struct machxo2_flash userflash;
> +};
> +
> +struct machxo2_addr {
> + uint16_t first_page;
> + uint16_t nr_pages;
> + uint8_t skip_bytes;
> +};
> +
> +/* Values below taken from dataheet TN1246 Table 17-97
> + * For the max erase time we use the value for the config flash (always larger)
> + */
> +static struct machxo2_device_info machxo2_device_ids[] = {
> + {0x00, "MachXO2-256",
> + .cfg_pages = 575, .ufm_pages = 0, .max_erase_ms = 700},
> + {0x10, "MachXO2-640",
> + .cfg_pages = 1151, .ufm_pages = 192, .max_erase_ms = 1100},
> + {0x20, "MachXO2-1200 / MachXO2-640U",
> + .cfg_pages = 2175, .ufm_pages = 512, .max_erase_ms = 1400 },
> + {0x30, "MachXO2-2000 / MachXO2-1200U",
> + .cfg_pages = 3198, .ufm_pages = 640, .max_erase_ms = 1900},
> + {0x40, "MachXO2-4000 / MachXO2-2000U",
> + .cfg_pages = 5758, .ufm_pages = 768, .max_erase_ms = 3100},
> + {0x50, "MachXO2-7000",
> + .cfg_pages = 9212, .ufm_pages = 2048, .max_erase_ms = 4800},
> +};
> +
> +
> +static const struct machxo2_flash_descriptor machxo2_userflash_descr = {
> + .name = "userflash",
> + .read_cmd = MACHXO2_CMD_READ_UFM,
> + .write_cmd = MACHXO2_CMD_WRITE_UFM,
> + .set_addr_param = 0x40,
> + .erase_param = 0x08,
> +};
> +
> +static const struct machxo2_flash_descriptor machxo2_configflash_descr = {
> + .name = "configflash",
> + .read_cmd = MACHXO2_CMD_READ_CONFIG_FLASH,
> + .write_cmd = MACHXO2_CMD_WRITE_CONFIG_FLASH,
> + .set_addr_param = 0x00,
> + .erase_param = 0x04,
> +};
> +
> +
> +/* ============================================================= */
> +/* Helpers */
> +/* ============================================================= */
> +static void machxo2_lock(struct machxo2 *machxo2)
> +{
> + if (!mutex_trylock(&machxo2->lock)) {
> + dev_dbg(machxo2->dev, "wait for %s from %pf\n",
> + __func__, __builtin_return_address(0));
> +
dev_dbg(machxo2->dev, "wait for %s from %pf\n",
__func__, __builtin_return_address(0));
> + mutex_lock(&machxo2->lock);
> + }
> + dev_dbg(machxo2->dev, "%s from %pf\n",
> + __func__, __builtin_return_address(0));
> +}
> +
dev_dbg(machxo2->dev, "%s from %pf\n",
__func__, __builtin_return_address(0));
> +static void machxo2_unlock(struct machxo2 *machxo2)
> +{
> + dev_dbg(machxo2->dev, "%s from %pf\n",
> + __func__, __builtin_return_address(0));
dev_dbg(machxo2->dev, "%s from %pf\n",
__func__, __builtin_return_address(0));
> + mutex_unlock(&machxo2->lock);
> +}
> +
> +
> +static int machxo2_transceive(
> + struct machxo2 *machxo2,
> + const void *send_buf, unsigned send_len,
> + void *recv_buf, unsigned recv_len)
static int machxo2_transceive(struct machxo2 *machxo2,
const void *send_buf, unsigned send_len,
void *recv_buf, unsigned recv_len)
> +{
> + bool need_lock;
> + int ret;
> +
> + if (!machxo2)
> + return -EINVAL;
> +
> + /* Simplify single message case by allowing caller to not take lock */
> + need_lock = !mutex_is_locked(&machxo2->lock);
> + if (need_lock)
> + machxo2_lock(machxo2);
> +
> + print_hex_dump_debug("machxo2: send ", DUMP_PREFIX_NONE,
> + 32, 1, send_buf, send_len, false);
dto...
> + ret = machxo2->busops->transceive(machxo2->dev,
> + send_buf, send_len,
dto...
> + recv_buf, recv_len);
> + if (!ret && recv_buf)
> + print_hex_dump_debug("machxo2: recv ", DUMP_PREFIX_NONE,
> + 32, 1, recv_buf, recv_len, false);
dto...
> +
> + if (need_lock)
> + machxo2_unlock(machxo2);
> +
> + return ret;
> +}
> +
> +static int machxo2_read_device_id(struct machxo2 *machxo2, uint8_t *buf)
> +{
> + uint8_t cmd[] = MACHXO2_CMD_READ_DEVICE_ID;
> +
> + return machxo2_transceive(machxo2, cmd, sizeof(cmd),
> + buf, MACHXO2_DEVICE_ID_LEN);
dto...
> +}
> +
> +static int machxo2_read_trace_id(struct machxo2 *machxo2, uint8_t *buf)
> +{
> + uint8_t cmd[] = MACHXO2_CMD_READ_TRACE_ID;
> +
> + return machxo2_transceive(machxo2, cmd, sizeof(cmd),
> + buf, MACHXO2_TRACE_ID_LEN);
dto...
> +}
> +
> +static int machxo2_read_usercode(struct machxo2 *machxo2, uint8_t *buf)
> +{
> + uint8_t cmd[] = MACHXO2_CMD_READ_USERCODE;
> +
> + return machxo2_transceive(machxo2, cmd, sizeof(cmd),
> + buf, MACHXO2_USERCODE_LEN);
dto...
> +}
> +
> +static const struct machxo2_device_info *machxo2_parse_device_id(uint8_t *buf)
> +{
> + int i;
> +
> + if (buf[0] != 0x01 || buf[1] != 0x2B || buf[3] != 0x43)
> + return NULL;
> +
> + for (i = 0; i < ARRAY_SIZE(machxo2_device_ids); i++) {
> + if ((buf[2] & 0x7F) == machxo2_device_ids[i].b2)
> + return &machxo2_device_ids[i];
> + }
> + return NULL;
> +}
> +
> +static int machxo2_identify_device(struct machxo2 *machxo2)
> +{
> + uint8_t trace_id[MACHXO2_TRACE_ID_LEN];
> + uint8_t usercode[MACHXO2_USERCODE_LEN];
> + int ret;
> +
> + ret = machxo2_read_device_id(machxo2, machxo2->device_id);
> + if (ret)
> + return ret;
> +
> + ret = machxo2_read_trace_id(machxo2, trace_id);
> + if (ret)
> + return ret;
> +
> + ret = machxo2_read_usercode(machxo2, usercode);
> + if (ret)
> + return ret;
> +
> + machxo2->device_info = machxo2_parse_device_id(machxo2->device_id);
> + if (!machxo2->device_info)
> + goto unsupported_id;
> +
> + dev_info(machxo2->dev,
> + "Found Lattice FPGA %s (%s) Trace ID=%*ph Usercode=%*ph\n",
> + machxo2->device_info->name,
> + machxo2->device_id[2] & 0x80 ? "HC" : "HE/ZE",
> + sizeof(trace_id), trace_id,
> + sizeof(usercode), usercode);
dto...
> + return 0;
> +
> +unsupported_id:
> + dev_err(machxo2->dev, "Unsupported device id: %*ph\n",
> + sizeof(machxo2->device_id), machxo2->device_id);
dto...
> + return -ENODEV;
> +}
> +
> +/* ============================================================= */
> +/* MTD */
> +/* ============================================================= */
> +static void machxo2_make_address(struct mtd_info *mtd,
> + uint32_t from, uint32_t len, struct machxo2_addr *addr)
> +{
> + addr->first_page = from / MACHXO2_PAGESIZE;
> + addr->skip_bytes = from % MACHXO2_PAGESIZE;
> +
> + addr->nr_pages = len / MACHXO2_PAGESIZE;
> + if (len % MACHXO2_PAGESIZE)
> + addr->nr_pages++;
> +
> + if (addr->skip_bytes && len > MACHXO2_PAGESIZE - addr->skip_bytes)
> + addr->nr_pages++;
> +
> + dev_dbg(&mtd->dev, "First page=%u nr_pages=%u skip_bytes=%u\n",
> + addr->first_page, addr->nr_pages, addr->skip_bytes);
dto...
> +}
> +
> +
> +static int machxo2_check_error_code(struct device *dev,
> + uint32_t status, const char *operation)
dto...
> +{
> + int err;
> +
> + if (!(status & MACHXO2_STATUS_FAILED))
> + return 0;
> +
> +
> + err = (status & MACHXO2_STATUS_ERR_MASK) >> MACHXO2_STATUS_ERR_SHIFT;
> + dev_err(dev, "Failed %s err=0x%x\n", operation, err);
> +
> + return -EIO;
> +}
> +
> +static int machxo2_read_status(struct machxo2 *machxo2, uint32_t *status_out)
> +{
> + uint8_t cmd[] = MACHXO2_CMD_READ_STATUS_REGSITER;
> + uint32_t status;
> + int ret;
> +
> + ret = machxo2_transceive(machxo2,
> + cmd, sizeof(cmd),
> + &status, sizeof(status));
> + if (!ret)
> + *status_out = be32_to_cpu(status);
> +
> + return ret;
> +}
> +
> +static int machxo2_flash_wait_not_busy(
> + struct machxo2_flash *flash,
> + unsigned poll_us, int retries,
> + const char *operation)
dto...
> +{
> + uint32_t status;
> + int i, ret;
> +
> + for (i = 0; i < retries; i++) {
> + ret = machxo2_read_status(flash->machxo2, &status);
> + if (ret)
> + return ret;
> +
> + if (!(status & MACHXO2_STATUS_BUSY)) {
> + dev_dbg(&flash->mtd.dev, "Wait %s done loop %d\n",
> + operation, i);
> + return machxo2_check_error_code(&flash->mtd.dev,
> + status, operation);
> + }
> +
> + if (poll_us < 1000)
> + udelay(poll_us);
> + else
> + msleep(poll_us / 1000);
> + }
> + return -ETIMEDOUT;
> +}
> +
> +static int machxo2_flash_enable_interface(struct machxo2_flash *flash)
> +{
> + uint8_t cmd[] = MACHXO2_CMD_ENABLE_CONFIG_INTERFACE;
> + int ret;
> +
> + ret = machxo2_transceive(flash->machxo2, cmd, sizeof(cmd), NULL, 0);
> + if (ret)
> + return ret;
> +
> + /* Datasheet: Enable time 5us
> + * So poll every 2us with 5 max polls */
> + return machxo2_flash_wait_not_busy(flash, 2, 5, "enable");
> +}
> +
> +static int machxo2_flash_disable_interface(struct machxo2_flash *flash)
> +{
> + struct machxo2 *machxo2 = flash->machxo2;
> + uint8_t disable[] = MACHXO2_CMD_DISABLE_CONFIG_INTERFACE;
> + uint8_t bypass[] = MACHXO2_CMD_BYPASS;
> + int ret;
> +
> + ret = machxo2_transceive(machxo2, disable, sizeof(disable), NULL, 0);
> + if (ret)
> + return ret;
> +
> + return machxo2_transceive(machxo2, bypass, sizeof(bypass), NULL, 0);
> +}
> +
> +
> +/* Do a transceive wrapped in enable, disable */
> +static int machxo2_trasceive_wait_with_enable(struct machxo2 *machxo2,
> + const void *send_buf, unsigned send_len,
> + void *recv_buf, unsigned recv_len,
> + unsigned poll_us, int retries,
> + const char *name)
> +{
> + int ret;
> +
> + machxo2_lock(machxo2);
> +
> + ret = machxo2_flash_enable_interface(&machxo2->configflash);
> + if (ret)
> + goto out;
> +
> + ret = machxo2_transceive(machxo2, send_buf, send_len,
> + recv_buf, recv_len);
> +
> + if (!ret && poll_us)
> + ret = machxo2_flash_wait_not_busy(&machxo2->configflash,
> + poll_us, retries,
> + name);
> +
> + machxo2_flash_disable_interface(&machxo2->configflash);
> +
> +out:
> + machxo2_unlock(machxo2);
> + return ret;
> +}
> +
> +static int machxo2_set_page_address(struct machxo2_flash *flash, uint16_t page)
> +{
> + uint8_t cmd[] = { MACHXO2_CMD_SET_ADDR, 0x00, 0x00, 0x00,
> + flash->descr->set_addr_param, 0x00,
> + (page & 0x3F00) >> 8,
> + (page & 0xFF)};
> +
> + return machxo2_transceive(flash->machxo2, cmd, sizeof(cmd), NULL, 0);
> +}
> +
> +
> +static int machxo2_write_page(struct machxo2_flash *flash, const uint8_t *buf)
> +{
> + uint8_t cmd[MACHXO2_PAGESIZE + 4] = {
> + flash->descr->write_cmd, 0x00, 0x00, 0x01 };
> + int ret;
> +
> + memcpy(cmd + 4, buf, MACHXO2_PAGESIZE);
> +
> + ret = machxo2_transceive(flash->machxo2, cmd, sizeof(cmd), NULL, 0);
> + if (ret)
> + return ret;
> +
> + /* Datasheet: Write time 200us
> + * So poll every 50us with 10 max polls */
> + return machxo2_flash_wait_not_busy(flash, 50, 10, "write");
> +}
> +
> +
> +static int machxo2_flash_read_pages(
> + struct machxo2_flash *flash, uint16_t nr_pages, uint8_t *buf)
> +{
> + uint8_t cmd[] = { flash->descr->read_cmd, 0x10,
> + (nr_pages & 0x3F00) >> 8,
> + (nr_pages & 0xFF)};
> +
> + return machxo2_transceive(flash->machxo2,
> + cmd, sizeof(cmd), buf, nr_pages * MACHXO2_PAGESIZE);
> +}
> +
> +static int machxo2_mtd_read(struct mtd_info *mtd,
> + loff_t from, size_t len,
> + size_t *retlen, u_char *buf)
> +{
> + struct machxo2_flash *flash = mtd->priv;
> + struct machxo2 *machxo2 = flash->machxo2;
> + int ret;
> + struct machxo2_addr addr;
> +
> + dev_dbg(&mtd->dev, "read: from=%llu size=%zu\n", from, len);
> +
> + machxo2_lock(machxo2);
> +
> + ret = machxo2_flash_enable_interface(flash);
> + if (ret)
> + goto out;
> +
> + machxo2_make_address(mtd, from, len, &addr);
> + ret = machxo2_set_page_address(flash, addr.first_page);
> + if (ret)
> + goto out_disable;
> +
> + if (addr.nr_pages == 1 && addr.skip_bytes == 0) {
> + ret = machxo2_flash_read_pages(flash, 1, buf);
> + } else {
> + /* Warning: this will not work for I2C mode which returns
> + * data differently for > 1 page reads.
> + * However I2C mode is not implemented yet */
> + uint8_t *tmp;
> +
> + /* For > 1 page read chip returns a dummp page first */
> + if (addr.nr_pages > 1) {
> + addr.nr_pages++;
> + addr.skip_bytes += MACHXO2_PAGESIZE;
> + }
> +
> + tmp = kmalloc(addr.nr_pages * MACHXO2_PAGESIZE, GFP_KERNEL);
> + if (!tmp) {
> + ret = -ENOMEM;
> + goto out_disable;
> + }
> +
> + ret = machxo2_flash_read_pages(flash, addr.nr_pages, tmp);
> + if (!ret)
> + memcpy(buf, tmp + addr.skip_bytes, len);
> + kfree(tmp);
> + }
> + if (!ret)
> + *retlen = len;
> +
> +out_disable:
> + machxo2_flash_disable_interface(flash);
> +
> +out:
> + machxo2_unlock(machxo2);
> + return ret;
> +}
> +
> +
> +static int machxo2_mtd_write(struct mtd_info *mtd,
> + loff_t to, size_t len,
> + size_t *retlen, const u_char *buf)
> +{
> + struct machxo2_flash *flash = mtd->priv;
> + struct machxo2 *machxo2 = flash->machxo2;
> + int ret;
> + struct machxo2_addr addr;
> + uint8_t tmp[MACHXO2_PAGESIZE];
> + uint32_t remaining = len;
> +
> + dev_dbg(&mtd->dev, "write: to=%llu size=%zu\n", to, len);
> +
> + machxo2_lock(machxo2);
> +
> + ret = machxo2_flash_enable_interface(flash);
> + if (ret)
> + goto out;
> +
> + machxo2_make_address(mtd, to, len, &addr);
> + ret = machxo2_set_page_address(flash, addr.first_page);
> + if (ret)
> + goto out_disable;
> +
> + if (addr.skip_bytes > 0) {
> + /* Write the first partial page */
> + uint32_t count = MACHXO2_PAGESIZE - addr.skip_bytes;
> +
> + if (count > remaining)
> + count = remaining;
> + dev_dbg(&mtd->dev, "write: head partial page %d\n", count);
> +
> + ret = machxo2_flash_read_pages(flash, 1, tmp);
> + if (ret)
> + goto out_disable;
> +
> + ret = machxo2_set_page_address(flash, addr.first_page);
> + if (ret)
> + goto out_disable;
> +
> + memcpy(tmp + addr.skip_bytes, buf, count);
> + ret = machxo2_write_page(flash, tmp);
> + if (ret)
> + goto out_disable;
> +
> + buf += count;
> + remaining -= count;
> + }
> +
> + /* Writa all complete pages */
> + while (remaining >= MACHXO2_PAGESIZE) {
> + dev_dbg(&mtd->dev, "write: full page remaining=%d\n",
> + remaining);
> + ret = machxo2_write_page(flash, buf);
> + if (ret)
> + goto out_disable;
> + buf += MACHXO2_PAGESIZE;
> + remaining -= MACHXO2_PAGESIZE;
> + }
> +
> +
> + if (remaining > 0) {
> + /* Write final partial page */
> + dev_dbg(&mtd->dev, "write: partial page remaining=%d\n",
> + remaining);
> + ret = machxo2_flash_read_pages(flash, 1, tmp);
> + if (ret)
> + goto out_disable;
> +
> + memcpy(tmp, buf, remaining);
> +
> + ret = machxo2_set_page_address(flash,
> + addr.first_page + addr.nr_pages - 1);
> + if (ret)
> + goto out_disable;
> +
> + ret = machxo2_write_page(flash, tmp);
> + if (ret)
> + goto out_disable;
> +
> + remaining = 0;
> + }
> +
> +out_disable:
> + machxo2_flash_disable_interface(flash);
> + *retlen = len - remaining;
> +
> +out:
> + machxo2_unlock(machxo2);
> + dev_dbg(&mtd->dev, "write done ret=%d retlen=%zu", ret, *retlen);
> + return ret;
> +}
> +
> +
> +static int machxo2_mtd_erase(struct mtd_info *mtd,
> + struct erase_info *instr)
> +{
> + struct machxo2_flash *flash = mtd->priv;
> + struct machxo2 *machxo2 = flash->machxo2;
> + uint8_t cmd[] = { MACHXO2_CMD_ERASE, flash->descr->erase_param, 0, 0 };
> + int ret;
> + int pollms;
> +
> + dev_dbg(&mtd->dev, "erase\n");
> +
> + machxo2_lock(machxo2);
> +
> + ret = machxo2_flash_enable_interface(flash);
> + if (ret)
> + goto out;
> +
> + ret = machxo2_transceive(machxo2, cmd, sizeof(cmd), NULL, 0);
> + if (ret)
> + goto out_disable;
> +
> + pollms = 200;
> + ret = machxo2_flash_wait_not_busy(flash,
> + pollms * 1000,
> + (machxo2->device_info->max_erase_ms + 500) / pollms,
> + "erase");
> +
> +out_disable:
> + machxo2_flash_disable_interface(flash);
> +
> +out:
> + machxo2_unlock(machxo2);
> +
> + dev_dbg(&mtd->dev, "erase done ret=%d", ret);
> + if (!ret) {
> + instr->state = MTD_ERASE_DONE;
> + mtd_erase_callback(instr);
> + }
> +
> + return ret;
> +}
> +
> +static int machxo2_flash_add(struct machxo2_flash *flash)
> +{
> + struct device *dev = flash->machxo2->dev;
> + struct mtd_info *mtd = &flash->mtd;
> + struct device_node *np;
> + int ret;
> +
> + np = of_find_node_by_name(dev->of_node, flash->descr->name);
> + if (of_property_read_string(np, "linux,mtd-name", &mtd->name)) {
> + flash->name = kasprintf(GFP_KERNEL, "%s-%s",
> + dev_name(dev), flash->descr->name);
> + if (!flash->name)
> + return -ENOMEM;
> +
> + mtd->name = flash->name;
> + }
> +
> + mtd->size = flash->nr_pages * MACHXO2_PAGESIZE;
> + mtd->erasesize = mtd->size;
> + mtd->writesize = MACHXO2_PAGESIZE;
> + mtd->owner = THIS_MODULE;
> + mtd->type = MTD_NANDFLASH;
> + mtd->flags = MTD_WRITEABLE;
> + mtd->_erase = machxo2_mtd_erase;
> + mtd->_read = machxo2_mtd_read;
> + mtd->_write = machxo2_mtd_write;
> + mtd->priv = flash;
> + mtd->dev.parent = dev;
> +
> + ret = mtd_device_register(mtd, NULL, 0);
> + if (ret) {
> + dev_err(dev, "Unable to register MTD device, aborting!\n");
> + goto out_err;
> + }
> +
> + dev_info(dev, "mtd%d: (%s) size %uKiB.\n",
> + mtd->index, mtd->name,
> + (uint32_t)(mtd->size >> 10));
> + return 0;
> +
> +out_err:
> + kfree(flash->name);
> + return ret;
> +}
> +
> +static void machxo2_flash_remove(struct machxo2_flash *flash)
> +{
> + mtd_device_unregister(&flash->mtd);
> + kfree(flash->name);
> +}
> +
> +
> +/* ============================================================= */
> +/* Sysfs */
> +/* ============================================================= */
> +
> +static ssize_t show_device_id(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct machxo2 *machxo2 = dev_get_drvdata(dev);
> +
> + return sprintf(buf, "%*ph\n",
> + sizeof(machxo2->device_id), machxo2->device_id);
> +}
> +
> +static ssize_t show_device_name(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct machxo2 *machxo2 = dev_get_drvdata(dev);
> + const struct machxo2_device_info *found;
> +
> + found = machxo2_parse_device_id(machxo2->device_id);
> +
> + return sprintf(buf, "%s\n", found ? found->name : "unknown");
> +}
> +
> +
> +static ssize_t show_trace_id(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct machxo2 *machxo2 = dev_get_drvdata(dev);
> + uint8_t trace_id[MACHXO2_TRACE_ID_LEN];
> + int ret;
> +
> + ret = machxo2_read_trace_id(machxo2, trace_id);
> + if (ret)
> + return ret;
> + return sprintf(buf, "%*ph\n",
> + sizeof(trace_id), trace_id);
> +}
> +
> +static ssize_t show_usercode(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct machxo2 *machxo2 = dev_get_drvdata(dev);
> + uint8_t usercode[MACHXO2_USERCODE_LEN];
> + int ret;
> +
> + ret = machxo2_read_usercode(machxo2, usercode);
> + if (ret)
> + return ret;
> +
> + return sprintf(buf, "%*ph\n",
> + sizeof(usercode), usercode);
> +}
> +
> +static ssize_t show_running(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct machxo2 *machxo2 = dev_get_drvdata(dev);
> + uint32_t status;
> + int ret;
> +
> + /* Reading status without enabling interface gives RAM state
> + * Hence DONE => FPGA running */
> + ret = machxo2_read_status(machxo2, &status);
> + if (ret)
> + return ret;
> +
> + return sprintf(buf, "%d\n", status & MACHXO2_STATUS_DONE ? 1 : 0);
> +}
> +
> +
> +static ssize_t show_prog_done(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct machxo2 *machxo2 = dev_get_drvdata(dev);
> + uint32_t status;
> + int ret;
> +
> + machxo2_lock(machxo2);
> +
> + /* Reading status after enabling interface gives FLASH state
> + * Hence DONE => config FLASH programmed */
> + ret = machxo2_flash_enable_interface(&machxo2->configflash);
> + if (ret)
> + goto out;
> +
> + ret = machxo2_read_status(machxo2, &status);
> + machxo2_flash_disable_interface(&machxo2->configflash);
> +
> + if (ret)
> + return ret;
> +
> + ret = sprintf(buf, "%d\n", status & MACHXO2_STATUS_DONE ? 1 : 0);
> +
> +out:
> + machxo2_unlock(machxo2);
> + return ret;
> +}
> +
> +static ssize_t store_prog_done(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + struct machxo2 *machxo2 = dev_get_drvdata(dev);
> + uint8_t cmd[] = MACHXO2_CMD_PROGRAM_DONE;
> + int ret;
> +
> + /* Datasheet: Write time 200us so poll every 50us with 10 max polls */
> + ret = machxo2_trasceive_wait_with_enable(machxo2,
> + cmd, sizeof(cmd), NULL, 0, 50, 10, "set_prog_done");
> +
> + if (!ret)
> + ret = count;
> +
> + return ret;
> +}
> +
> +static ssize_t store_refresh(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + struct machxo2 *machxo2 = dev_get_drvdata(dev);
> + uint8_t cmd[] = MACHXO2_CMD_REFRESH;
> + uint32_t status;
> + int ret;
> +
> + ret = machxo2_transceive(machxo2, cmd, sizeof(cmd), NULL, 0);
> + if (ret)
> + return ret;
> +
> + msleep(25); /* Datasheet gives 3.8ms tREFRESH for largest device */
> +
> + ret = machxo2_read_status(machxo2, &status);
> + if (ret)
> + return ret;
> +
> + if (status & MACHXO2_STATUS_DONE)
> + return count;
> +
> + /* The chip didn't like the configuration data */
> + dev_err(dev, "Refresh failed status=%d\n",
> + (status & MACHXO2_STATUS_ERR_MASK) >> MACHXO2_STATUS_ERR_SHIFT);
> +
> + return -EBADMSG;
> +}
> +
> +
> +static DEVICE_ATTR(device_id, S_IRUGO, show_device_id, NULL);
> +static DEVICE_ATTR(name, S_IRUGO, show_device_name, NULL);
> +static DEVICE_ATTR(trace_id, S_IRUGO, show_trace_id, NULL);
> +static DEVICE_ATTR(usercode, S_IRUGO, show_usercode, NULL);
> +static DEVICE_ATTR(running, S_IRUGO, show_running, NULL);
> +static DEVICE_ATTR(prog_done, S_IRUGO | S_IWUSR,
> + show_prog_done, store_prog_done);
> +static DEVICE_ATTR(refresh, S_IWUSR, NULL, store_refresh);
> +
> +static struct attribute *machxo2_sysfs_entries[] = {
> + &dev_attr_device_id.attr,
> + &dev_attr_name.attr,
> + &dev_attr_trace_id.attr,
> + &dev_attr_usercode.attr,
> + &dev_attr_running.attr,
> + &dev_attr_prog_done.attr,
> + &dev_attr_refresh.attr,
> + NULL,
> +};
> +
> +static struct attribute_group machxo2_attr_group = {
> + .attrs = machxo2_sysfs_entries,
> +};
> +
> +/* ============================================================= */
> +/* Init */
> +/* ============================================================= */
> +
> +struct machxo2 *machxo2_create(struct device *dev,
> + struct machxo2_busops *busops)
> +{
> + struct machxo2 *machxo2;
> + struct machxo2_flash *flash;
> + int ret;
> +
> + machxo2 = devm_kzalloc(dev, sizeof(*machxo2), GFP_KERNEL);
> + if (!machxo2)
> + return ERR_PTR(-ENOMEM);
> +
> + machxo2->dev = dev;
> + machxo2->busops = busops;
> +
> + mutex_init(&machxo2->lock);
> +
> + machxo2_lock(machxo2);
> + ret = machxo2_identify_device(machxo2);
> + machxo2_unlock(machxo2);
> +
> + if (ret)
> + goto fail_identify;
> +
> +#ifdef CONFIG_MTD_MACHXO2_CONFIGFLASH
> + flash = &machxo2->configflash;
> + flash->machxo2 = machxo2;
> + flash->descr = &machxo2_configflash_descr;
> + flash->nr_pages = machxo2->device_info->cfg_pages;
> + ret = machxo2_flash_add(flash);
> + if (ret)
> + goto fail_configflash;
> +#endif
> +
> +#ifdef CONFIG_MTD_MACHXO2_USERFLASH
> + if (machxo2->device_info->ufm_pages > 0) {
> + flash = &machxo2->userflash;
> + flash->machxo2 = machxo2;
> + flash->descr = &machxo2_userflash_descr;
> + flash->nr_pages = machxo2->device_info->ufm_pages;
> + ret = machxo2_flash_add(flash);
> + if (ret)
> + goto fail_userflash;
> + }
> +#endif
> +
> + ret = sysfs_create_group(&machxo2->dev->kobj, &machxo2_attr_group);
> + if (ret)
> + goto fail_sysfs;
> +
> + return machxo2;
> +
> +fail_sysfs:
> + machxo2_flash_remove(&machxo2->userflash);
> +fail_userflash:
> + machxo2_flash_remove(&machxo2->configflash);
> +fail_configflash:
> +fail_identify:
> + return ERR_PTR(ret);
> +}
> +EXPORT_SYMBOL_GPL(machxo2_create);
> +
> +void machxo2_destroy(struct machxo2 *machxo2)
> +{
> + machxo2_flash_remove(&machxo2->userflash);
> + sysfs_remove_group(&machxo2->dev->kobj, &machxo2_attr_group);
> +}
> +EXPORT_SYMBOL_GPL(machxo2_destroy);
> +
> +MODULE_DESCRIPTION("MTD driver for Lattice MachXO2 family FPGAs");
> +MODULE_AUTHOR("Martin Fuzzey <mfuzzey at parkeon.com>");
> +MODULE_LICENSE("GPL v2");
> diff --git a/drivers/mtd/devices/machxo2-spi.c b/drivers/mtd/devices/machxo2-spi.c
> new file mode 100644
> index 0000000..e4421fd
> --- /dev/null
> +++ b/drivers/mtd/devices/machxo2-spi.c
> @@ -0,0 +1,114 @@
> +/*
> + * SPI bus interface for Lattice MachXO2 family FPGAs
> + *
> + * Copyright (C) 2014 Parkeon
> + * Martin Fuzzey <mfuzzey at parkeon.com>
> + *
> + * This program is free software; you can redistribute it and/or modify it under
> + * the terms of the GNU General Public License version 2 as published by the
> + * Free Software Foundation.
> + */
> +
> +#include <linux/err.h>
> +#include <linux/module.h>
> +#include <linux/spi/spi.h>
> +
> +#include "machxo2.h"
> +
> +
> +static const struct spi_device_id machxo2_spi_device_id[] = {
> + {
> + .name = "machxo2",
> + }, {
> + /* sentinel */
> + }
> +};
> +MODULE_DEVICE_TABLE(spi, machxo2_spi_device_id);
> +
> +static const struct of_device_id machxo2_dt_ids[] = {
> + { .compatible = "lattice,machxo2"},
> + { /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, machxo2_dt_ids);
> +
move the device ids after probe()/remove()...
> +
> +static int machxo2_spi_transceive(
> + struct device *dev,
> + const void *send_buf, unsigned send_len,
> + void *recv_buf, unsigned recv_len)
proper alignment...
static int machxo2_spi_transceive(struct device *dev,
const void *send_buf, unsigned send_len,
void *recv_buf, unsigned recv_len)
> +{
> + struct spi_device *spi = to_spi_device(dev);
> + struct spi_message msg;
> + struct spi_transfer xfers[] = {
> + {
> + .tx_buf = send_buf,
> + .len = send_len,
> + },
> + {
> + .rx_buf = recv_buf,
> + .len = recv_len,
> + }
> + };
> +
> + spi_message_init_with_transfers(&msg, xfers, ARRAY_SIZE(xfers));
> +
> + return spi_sync(spi, &msg);
> +}
> +
> +
> +static struct machxo2_busops machxo2_spi_busops = {
> + .transceive = machxo2_spi_transceive,
> +};
> +
> +
> +static int machxo2_spi_probe(struct spi_device *spi)
> +{
> + struct machxo2 *machxo2;
> +
> + machxo2 = machxo2_create(&spi->dev, &machxo2_spi_busops);
> + if (IS_ERR(machxo2))
> + return PTR_ERR(machxo2);
> +
> + spi_set_drvdata(spi, machxo2);
> +
> + return 0;
> +}
> +
> +
> +static int machxo2_spi_remove(struct spi_device *spi)
> +{
> + struct machxo2 *machxo2 = spi_get_drvdata(spi);
> +
> + machxo2_destroy(machxo2);
> +
> + return 0;
> +}
> +
> +
Every driver list the device ids here....
> +static struct spi_driver machxo2_spi_driver = {
> + .id_table = machxo2_spi_device_id,
> + .driver = {
> + .name = "machxo2",
> + .owner = THIS_MODULE,
we can drop owner field...
> + .of_match_table = machxo2_dt_ids,
> + },
> + .probe = machxo2_spi_probe,
> + .remove = machxo2_spi_remove,
> +};
> +
> +
> +static int __init machxo2_init(void)
> +{
> + return spi_register_driver(&machxo2_spi_driver);
> +}
> +subsys_initcall(machxo2_init);
> +
> +static void __exit machxo2_exit(void)
> +{
> + spi_unregister_driver(&machxo2_spi_driver);
> +}
> +module_exit(machxo2_exit);
> +
module_spi_driver()...?
> +MODULE_AUTHOR("Martin Fuzzey <mfuzzey at parkeon.com>");
> +MODULE_DESCRIPTION("MachXO2 FPGA SPI bus driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/mtd/devices/machxo2.h b/drivers/mtd/devices/machxo2.h
> new file mode 100644
> index 0000000..e0213d0
> --- /dev/null
> +++ b/drivers/mtd/devices/machxo2.h
> @@ -0,0 +1,24 @@
> +/*
> + * Copyright 2014 Parkeon
> + * Martin Fuzzey <mfuzzey at parkeon.com>
> + *
> + * This program is free software; you can redistribute it and/or modify it under
> + * the terms of the GNU General Public License version 2 as published by the
> + * Free Software Foundation.
> + */
> +#ifndef __DRIVERS_MTD_MACHXO2_H
> +#define __DRIVERS_MTD_MACHXO2_H
> +
> +struct machxo2;
> +struct machxo2_busops {
> + int (*transceive)(
> + struct device *dev,
> + const void *send_buf, unsigned send_len,
> + void *recv_buf, unsigned recv_len);
> +};
> +
proper indentation...
int (*transceive)(struct device *dev,
const void *send_buf, unsigned send_len,
void *recv_buf, unsigned recv_len);
> +struct machxo2 *machxo2_create(struct device *dev,
> + struct machxo2_busops *busops);
> +
struct machxo2 *machxo2_create(struct device *dev,
struct machxo2_busops *busops);
This patch has coding style problems.. run checkpatch on this patch..
Thanx.
--
Regards,
Varka Bhadram.
More information about the linux-mtd
mailing list