From patchwork Fri Oct 7 15:22:02 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Pantelis Antoniou X-Patchwork-Id: 9366365 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id C4A836075E for ; Fri, 7 Oct 2016 15:31:44 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id B35EB296E2 for ; Fri, 7 Oct 2016 15:31:44 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id A7AB9296F8; Fri, 7 Oct 2016 15:31:44 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-6.3 required=2.0 tests=BAYES_00,DKIM_SIGNED, RCVD_IN_DNSWL_HI, RCVD_IN_SORBS_SPAM, T_DKIM_INVALID autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 56EF1296F7 for ; Fri, 7 Oct 2016 15:31:43 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S932421AbcJGPbl (ORCPT ); Fri, 7 Oct 2016 11:31:41 -0400 Received: from mail-wm0-f43.google.com ([74.125.82.43]:38186 "EHLO mail-wm0-f43.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1757035AbcJGPY2 (ORCPT ); Fri, 7 Oct 2016 11:24:28 -0400 Received: by mail-wm0-f43.google.com with SMTP id i130so46099988wmg.1 for ; Fri, 07 Oct 2016 08:24:27 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=konsulko.com; s=google; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=mSQv3BuZzAHhmIaGExUO6IEHTY0QUxNcfku7X+QZkJY=; b=bceYMPtgKHWytXiGuYprvQ84FFoqBngBkY/0SVrmhdBk4dPUvtK9leCl4Bl7pJlRQ0 25XUtPybm2k0zu0jQkHZYwgosJBeBQ9Y52CTRKLyHYOuEn9Ckmdu7fzuz8ihylPN1wzI /bIGV5orE92MUs0oXGi/9zxbYoJ9xxS4tdT2s= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=mSQv3BuZzAHhmIaGExUO6IEHTY0QUxNcfku7X+QZkJY=; b=lbAKCNrgWaM9ZAG9q8PKTk699FxjpYbFZ1FYzGfYiADIxqq5rshGCq6elS/SrVyd0K vI5tGQ/CMKC/VJ0wCGsqYo+VVSJHjceBZr1RcVmY2yQupJlB7Ujgt1OMlkHlv18OtgBM BPZ76bFwlPox/Ij+pWplYgtWhuvWtiBpFDKnG/eAKfPUOl8ackSh4TggQ3drwXNV5auk KvUHOSJkScCqo8Bd05tcTQHlu0WWAeOQbxxbu2pswfTpLnJQIw3k44LoVeBHtsZRS7lY pCOVbr9RGV+RynfuFK9nIyIgx9S2gPPZ755jYAHi/FJqZL5yHMu+1TPrXK2Tu5/cUP0w VXKQ== X-Gm-Message-State: AA6/9RmoZ/gUmjx2kDPEHkG8FLgL7VSH9I/rxLGZd+20PvyNmwLo4JRRKKfPhqRCp4Bjgg== X-Received: by 10.28.207.204 with SMTP id f195mr10108583wmg.29.1475853866127; Fri, 07 Oct 2016 08:24:26 -0700 (PDT) Received: from localhost.localdomain ([195.97.110.117]) by smtp.gmail.com with ESMTPSA id f2sm3491056wjr.2.2016.10.07.08.24.23 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Fri, 07 Oct 2016 08:24:25 -0700 (PDT) From: Pantelis Antoniou To: Lee Jones Cc: Rob Herring , Mark Rutland , Frank Rowand , David Woodhouse , Brian Norris , Wim Van Sebroeck , Jean Delvare , Georgi Vlaev , Guenter Roeck , JawaharBalaji Thirumalaisamy , Pantelis Antoniou , devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-mtd@lists.infradead.org, linux-watchdog@vger.kernel.org, linux-hwmon@vger.kernel.org Subject: [PATCH 5/7] mtd: ptx1kbf: Add PTX1K BootFPGA MTD driver Date: Fri, 7 Oct 2016 18:22:02 +0300 Message-Id: <1475853724-22557-6-git-send-email-pantelis.antoniou@konsulko.com> X-Mailer: git-send-email 1.9.1 In-Reply-To: <1475853724-22557-1-git-send-email-pantelis.antoniou@konsulko.com> References: <1475853724-22557-1-git-send-email-pantelis.antoniou@konsulko.com> Sender: linux-hwmon-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-hwmon@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP From: Georgi Vlaev This patch adds a MTD driver for configuration updates of the Xilinx Spartan 3AN based FPGAs on the PTX1K RE boards. The driver is client of the ptx1k-bootfpga driver. Signed-off-by: Georgi Vlaev [Ported from Juniper kernel] Signed-off-by: Pantelis Antoniou --- drivers/mtd/devices/Kconfig | 10 + drivers/mtd/devices/Makefile | 1 + drivers/mtd/devices/jnx_ptx1kbf_mtd.c | 677 ++++++++++++++++++++++++++++++++++ 3 files changed, 688 insertions(+) create mode 100644 drivers/mtd/devices/jnx_ptx1kbf_mtd.c diff --git a/drivers/mtd/devices/Kconfig b/drivers/mtd/devices/Kconfig index f5a9032..25a1c4a 100644 --- a/drivers/mtd/devices/Kconfig +++ b/drivers/mtd/devices/Kconfig @@ -155,6 +155,16 @@ config MTD_SAM_FLASH This driver can also be built as a module. When it is so the name of the module is flash-sam. +config MTD_JNX_PTX1KBF + tristate "Juniper I2CS BootFPGA MTD driver" + depends on MFD_JUNIPER_PTX1KBF + help + This enables the MTD driver for the Juniper I2CS BootFPGA. + The driver is used to perform software upgrades of the RE's + Spartan 3AN XC3S50AN, XC3S200AN, XCS400AN, XC3S700AN and + XC3S1400AN based I2CS. The I2CS BootFPGA must implement the + remote upgrade software interface. + config JNX_PMB_NVRAM tristate "Juniper FPC PMB NVRAM Driver" depends on (PTXPMB_COMMON || JNX_PTX_NGPMB) diff --git a/drivers/mtd/devices/Makefile b/drivers/mtd/devices/Makefile index 7556311..e1adfa5 100644 --- a/drivers/mtd/devices/Makefile +++ b/drivers/mtd/devices/Makefile @@ -19,6 +19,7 @@ obj-$(CONFIG_MTD_ST_SPI_FSM) += st_spi_fsm.o obj-$(CONFIG_MTD_POWERNV_FLASH) += powernv_flash.o obj-$(CONFIG_MTD_SAM_FLASH) += sam-flash.o +obj-$(CONFIG_MTD_JNX_PTX1KBF) += jnx_ptx1kbf_mtd.o obj-$(CONFIG_JNX_PMB_NVRAM) += jnx_pmb_nvram.o CFLAGS_docg3.o += -I$(src) diff --git a/drivers/mtd/devices/jnx_ptx1kbf_mtd.c b/drivers/mtd/devices/jnx_ptx1kbf_mtd.c new file mode 100644 index 0000000..3bc17be --- /dev/null +++ b/drivers/mtd/devices/jnx_ptx1kbf_mtd.c @@ -0,0 +1,677 @@ +/* + * Juniper Networks PTX1K RCB I2CS Boot FPGA MTD driver + * FPGA upgrades of the Spartan3AN/XC3S700 based I2CS. + * + * Copyright (C) 2015 Juniper Networks. All rights reserved. + * Author: Georgi Vlaev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEFAULT_BUSY_TIMEOUT 5 /* default timeout (msec) */ +#define PAGE_BUFFER_TXFER 400 /* page->sram transfer (usec) */ + +/* FLASH_IF_CONTROL commands */ +#define CONTROL_READ_SID 0x80 +#define CONTROL_READ_DID 0x04 +#define CONTROL_READ_STATUS 0x08 +#define CONTROL_SECTOR_ERASE 0x20 +#define CONTROL_PAGE_ERASE 0x40 +#define CONTROL_PAGE_WRITE 0x02 +#define CONTROL_PAGE_READ 0x01 +#define CONTROL_SP_READ 0x02 +#define CONTROL_SP_WRITE 0x08 +#define CONTROL_SP_UALL 0x01 + +/* FLASH_IF_STATUS bits */ +#define STATUS_BUSY BIT(0) +#define STATUS_ILLEGAL_WRITE BIT(1) +#define STATUS_ILLEGAL_ERASE BIT(2) +#define STATUS_ILLEGAL (STATUS_ILLEGAL_WRITE | STATUS_ILLEGAL_ERASE) +#define STATUS_POWER_2_ADDR BIT(3) +#define STATUS_SID_I2CS 0x1F /* I2CS Spartan3AN FPGA */ + +/* RU_CONFIG_CONTROL_STATUS bits */ +#define RU_CCS_USER_IMAGE BIT(7) +#define RU_CCS_RECONFIG BIT(5) + +#define MASK_SPARE_PAGE 0x1f +#define MASK_SPARE_SECTOR 0x1fff + +#define SECTOR_UNPROTECT 0x00 +#define SECTOR_PROTECT 0xff + +struct bfmtd_info { + const char *name; + u8 device_id; /* flash id as reported by the fpga */ + size_t flash_size; /* total flash size */ + size_t page_size; /* read/write page size */ + size_t sector_size; /* erase sector size */ + size_t nr_pages; /* total number of pages */ + size_t nr_sectors; /* total number of sectors */ + size_t writesize; /* write size */ + size_t writebufsize; /* internal fpga sram buffer size (bytes) */ + size_t spare_size; /* additional bytes per page in 'default' mode*/ + /* + * Timeouts are defined in the Xilinx "In-System Flash (ISF) Upgrade + * User Guide". Our values will add 20% on top of ISF ones + */ + u16 page_erase_tmo; /* page erase timeout (msec) */ + u16 sector_erase_tmo; /* sector erase timeout (msec) */ + u16 buffer_program_tmo; /* sram buffer program timeout (msec) */ +}; + +/* + * Definitions in power-of-2 addressing mode. + * Reconfigure sizes if default mode is detected. + */ +static struct bfmtd_info bfmtd_info_db[] = { + { + .name = "XC3S50AN", + .device_id = 0x22, + .flash_size = 256 * 512, + .page_size = 256, + .nr_pages = 512, + .nr_sectors = 4, + .sector_size = 256 * 128, + .writesize = 1, + .writebufsize = 256, + .spare_size = 8, + .page_erase_tmo = 38, + .sector_erase_tmo = 3000, + .buffer_program_tmo = 5, + }, + { + .name = "XC3S200AN/XC3S400AN", + .device_id = 0x24, + .flash_size = 256 * 2048, + .page_size = 256, + .nr_pages = 2048, + .nr_sectors = 8, + .sector_size = 256 * 256, + .writesize = 1, + .writebufsize = 256, + .spare_size = 8, + .page_erase_tmo = 38, + .sector_erase_tmo = 6000, + .buffer_program_tmo = 5, + }, + { + .name = "XC3S700AN", + .device_id = 0x25, + .flash_size = 256 * 4096, + .page_size = 256, + .nr_pages = 4096, + .nr_sectors = 16, + .sector_size = 256 * 256, + .writesize = 1, + .writebufsize = 256, + .spare_size = 8, + .page_erase_tmo = 42, + .sector_erase_tmo = 6000, + .buffer_program_tmo = 8, + }, + { + .name = "XC3S1400AN", + .device_id = 0x26, + .flash_size = 512 * 4096, + .page_size = 512, + .nr_pages = 4096, + .nr_sectors = 16, + .sector_size = 512 * 256, + .writesize = 1, + .writebufsize = 512, + .spare_size = 8, + .page_erase_tmo = 42, + .sector_erase_tmo = 6000, + .buffer_program_tmo = 8, + }, +}; + +struct bf_mtd { + void __iomem *base; + struct device *dev; + struct mtd_info mtd; + struct bfmtd_info *info; + struct mutex lock; +}; + +/* + * bfmtd_busy_wait() + * Wait for busy[0] flag in the FLASH_IF_STATUS register + */ +static int bfmtd_busy_wait(struct bf_mtd *bfmtd, unsigned long max_wait) +{ + u8 status; + unsigned long timeout = jiffies + msecs_to_jiffies(max_wait); + + udelay(50); + do { + status = ioread8(bfmtd->base + BOOT_FPGA_FLASH_IF_STATUS(3)); + if (!(status & STATUS_BUSY)) + return 0; + + if (status & STATUS_ILLEGAL) + return -EACCES; + + usleep_range(50, 100); + } while (time_before(jiffies, timeout)); + + return -ETIMEDOUT; +} + +/* + * bfmtd_read_status() + * Read FLASH_IF_STATUS regsters + */ +static int bfmtd_read_status(struct bf_mtd *bfmtd, u8 control_cmd, + u8 status_index) +{ + int ret; + + iowrite8(control_cmd, + bfmtd->base + BOOT_FPGA_FLASH_IF_CONTROL(3)); + + ret = bfmtd_busy_wait(bfmtd, DEFAULT_BUSY_TIMEOUT); + if (ret) + return ret; + + return ioread8(bfmtd->base + + BOOT_FPGA_FLASH_IF_STATUS(status_index)); +} + +/* Device ID */ +static inline int bfmtd_read_did(struct bf_mtd *bfmtd) +{ + return bfmtd_read_status(bfmtd, CONTROL_READ_DID, 1); +} + +/* Silicon ID */ +static inline int bfmtd_read_sid(struct bf_mtd *bfmtd) +{ + return bfmtd_read_status(bfmtd, CONTROL_READ_SID, 2); +} + +/* ^2 addressing mode */ +static inline int bfmtd_read_p2addr(struct bf_mtd *bfmtd) +{ + return bfmtd_read_status(bfmtd, CONTROL_READ_STATUS, 3); +} + +/* + * bfmtd_calc_flash_addr() + * + * We have to support 2 different addressing schemes. + * The page_size can be 256, 264, 512, 528. + * The page_size in the default addressing mode are 264 and 528. + * The same is valid for the sector addresses. + * Calculate the page/sector address from the page_size/sectro_size. + * + * Example (XC3S700AN): + * Page# Default addressing Power of 2 addressing + * 0 0x000 - 0x107 0x000 - 0xFF + * 1 0x200 - 0x307 0x100 - 0x1FF + * 2 0x400 - 0x507 0x200 - 0x2FF + */ +static u32 bfmtd_calc_flash_addr(u32 addr, u32 size, u32 mask) +{ + /* spare = 0,8,16 for pages and 0,1024,2048,4096 for sectors */ + u32 spare = size & mask; + + if (!spare) /* power-of-2 mode */ + return addr & ~(size - 1); + + /* default mode, use spare to get the page/sector address */ + addr /= size; + addr *= (spare << 6); + + return addr; +} + +/* + * bfmtd_read_page() + * Transfer a page from the flash into the sram buffer + */ +static int bfmtd_read_page(struct bf_mtd *bfmtd, u32 addr, u8 *buf, + size_t len) +{ + int ret; + u32 page_addr, page_ofs; + u16 buff_addr; + size_t retlen, page_size = bfmtd->info->page_size; + + if (len > page_size) + len = page_size; + + page_ofs = addr % page_size; + retlen = len - page_ofs; + + if (!retlen || addr + retlen > bfmtd->info->flash_size) + return -EINVAL; + + ret = bfmtd_busy_wait(bfmtd, DEFAULT_BUSY_TIMEOUT); + if (ret) + return ret; + + /* Calculate the page address */ + page_addr = bfmtd_calc_flash_addr(addr, page_size, MASK_SPARE_PAGE); + + /* Set page address, byte count and trigger write */ + iowrite32(be32_to_cpu(page_addr), + bfmtd->base + BOOT_FPGA_FLASH_IF_ADDR(0)); + /* byte count (N), denotes (N + 1) */ + iowrite32(be32_to_cpu(len - 1), + bfmtd->base + BOOT_FPGA_FLASH_IF_BYTE_COUNT(0)); + iowrite8(CONTROL_PAGE_READ, + bfmtd->base + BOOT_FPGA_FLASH_IF_CONTROL(3)); + + /* Wait the fpga the fetch the page into the sram buffer (400 usec) */ + ret = bfmtd_busy_wait(bfmtd, DEFAULT_BUSY_TIMEOUT); + if (ret) + return ret; + + for (buff_addr = page_ofs; buff_addr < len; buff_addr++) { + iowrite16(be16_to_cpu(buff_addr), + bfmtd->base + BOOT_FPGA_FLASH_IF_READ_BUF_ADDR_MSB); + *buf = ioread8(bfmtd->base + BOOT_FPGA_FLASH_IF_READ_BUF_DATA); + buf++; + } + + return retlen; +} + +/* + * bfmtd_is_protected() + * Check if the sector @addr is protected + */ +static int bfmtd_is_protected(struct bf_mtd *bfmtd, u32 addr) +{ + u8 protected_sectors[16]; + int n_sector, ret = 0; + u16 buff_addr; + + if (addr > bfmtd->info->flash_size) + return -EINVAL; + + n_sector = addr / bfmtd->info->sector_size; + + /* Read the protected sector array */ + iowrite8(CONTROL_SP_READ, + bfmtd->base + BOOT_FPGA_FLASH_IF_CONTROL(2)); + + ret = bfmtd_busy_wait(bfmtd, DEFAULT_BUSY_TIMEOUT); + if (ret) + return ret; + + for (buff_addr = 0; buff_addr < bfmtd->info->nr_sectors; buff_addr++) { + iowrite16(be16_to_cpu(buff_addr), + bfmtd->base + BOOT_FPGA_FLASH_IF_READ_BUF_ADDR_MSB); + protected_sectors[buff_addr] = + ioread8(bfmtd->base + BOOT_FPGA_FLASH_IF_READ_BUF_DATA); + } + + if (protected_sectors[n_sector] == SECTOR_PROTECT) { + dev_err(bfmtd->dev, "Sector #%d is protected\n", n_sector); + return -EACCES; + } + + return 0; +} + +/* + * bfmtd_erase_region() + * Erase pages or sectors. + * Note: The control (0xa5a5a5a5) words are written on page boundary. + */ +static int bfmtd_erase_region(struct bf_mtd *bfmtd, u32 addr) +{ + struct mtd_info *mtd = &bfmtd->mtd; + u32 flash_addr, mask, timeout; + u8 cmd; + int ret; + + if (addr > bfmtd->info->flash_size) + return -EINVAL; + + ret = bfmtd_is_protected(bfmtd, addr); + if (ret) + return ret; + + ret = bfmtd_busy_wait(bfmtd, DEFAULT_BUSY_TIMEOUT); + if (ret) + return ret; + + /* sector/page erase mode ? */ + if (bfmtd->info->page_size == mtd->erasesize) { + mask = MASK_SPARE_PAGE; + timeout = bfmtd->info->page_erase_tmo; + cmd = CONTROL_PAGE_ERASE; + } else { + mask = MASK_SPARE_SECTOR; + timeout = bfmtd->info->sector_erase_tmo; + cmd = CONTROL_SECTOR_ERASE; + } + + /* Calculate the page/sector address */ + flash_addr = bfmtd_calc_flash_addr(addr, mtd->erasesize, mask); + + /* Set page/sector address, byte count and trigger erase */ + iowrite32(be32_to_cpu(flash_addr), + bfmtd->base + BOOT_FPGA_FLASH_IF_ADDR(0)); + iowrite8(cmd, bfmtd->base + BOOT_FPGA_FLASH_IF_CONTROL(3)); + + /* Wait the fpga the flush the buffer */ + ret = bfmtd_busy_wait(bfmtd, timeout); + if (ret) + return ret; + + return 0; +} + +/* + * bfmtd_write_page() + * Write a page content in the sram buffer and then to flash device + */ +static int bfmtd_write_page(struct bf_mtd *bfmtd, u32 addr, const u8 *buf, + size_t len) +{ + int ret; + u32 page_addr, page_ofs; + u16 buff_addr; + size_t retlen, page_size = bfmtd->info->page_size; + + if (len > page_size) + len = page_size; + + page_ofs = addr % page_size; + retlen = len - page_ofs; + + if (!retlen || addr + retlen > bfmtd->info->flash_size) + return -EINVAL; + + ret = bfmtd_busy_wait(bfmtd, DEFAULT_BUSY_TIMEOUT); + if (ret) + return ret; + + /* Fill the internal fpga buffer */ + for (buff_addr = page_ofs; buff_addr < len; buff_addr++) { + iowrite16(be16_to_cpu(buff_addr), + bfmtd->base + BOOT_FPGA_FLASH_IF_WRITE_BUF_ADDR_MSB); + iowrite8(*buf, bfmtd->base + BOOT_FPGA_FLASH_IF_WRITE_BUF_DATA); + buf++; + } + + /* Calculate the page address */ + page_addr = bfmtd_calc_flash_addr(addr, page_size, MASK_SPARE_PAGE); + + /* Set page address, byte count and trigger write */ + iowrite32(be32_to_cpu(page_addr), + bfmtd->base + BOOT_FPGA_FLASH_IF_ADDR(0)); + iowrite32(be32_to_cpu(len - 1), + bfmtd->base + BOOT_FPGA_FLASH_IF_BYTE_COUNT(0)); + iowrite8(CONTROL_PAGE_WRITE, + bfmtd->base + BOOT_FPGA_FLASH_IF_CONTROL(3)); + + /* Wait the fpga the flush the buffer */ + ret = bfmtd_busy_wait(bfmtd, bfmtd->info->buffer_program_tmo); + if (ret) + return ret; + + return retlen; +} + +/* + * bfmtd_read() + * MTD read + */ +static int bfmtd_read(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, u8 *buf) +{ + struct bf_mtd *bfmtd = container_of(mtd, struct bf_mtd, mtd); + int ret, read = 0; + + mutex_lock(&bfmtd->lock); + while (len) { + ret = bfmtd_read_page(bfmtd, from, buf, len); + if (ret < 0) { + dev_err(bfmtd->dev, "RD @0x%llx, size %ld failed (%d)", + from, len, ret); + mutex_unlock(&bfmtd->lock); + return ret; + } + read += ret; + len -= ret; + from += ret; + buf += ret; + } + mutex_unlock(&bfmtd->lock); + + *retlen = read; + + return 0; +} + +/* + * bfmtd_write() + * MTD write + */ +static int bfmtd_write(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u8 *buf) +{ + struct bf_mtd *bfmtd = container_of(mtd, struct bf_mtd, mtd); + int ret, written = 0; + + mutex_lock(&bfmtd->lock); + while (len) { + ret = bfmtd_write_page(bfmtd, to, buf, len); + if (ret < 0) { + dev_err(bfmtd->dev, "WR @0x%llx, size %ld failed (%d)", + to, len, ret); + mutex_unlock(&bfmtd->lock); + return ret; + } + written += ret; + len -= ret; + to += ret; + buf += ret; + } + mutex_unlock(&bfmtd->lock); + + *retlen = written; + + return 0; +} + +/* + * bfmtd_erase() + * MTD erase + */ +static int bfmtd_erase(struct mtd_info *mtd, struct erase_info *ei) +{ + struct bf_mtd *bfmtd = container_of(mtd, struct bf_mtd, mtd); + u32 start_addr, end_addr, addr; + int len, ret = 0; + + len = ei->len; + start_addr = ei->addr; + end_addr = start_addr + len - 1; + + ei->state = MTD_ERASE_DONE; + addr = start_addr; + mutex_lock(&bfmtd->lock); + while (addr < end_addr) { + ret = bfmtd_erase_region(bfmtd, addr); + if (ret < 0) { + dev_err(bfmtd->dev, "Erase @0x%x, size %d failed (%d)", + addr, mtd->erasesize, ret); + ei->state = MTD_ERASE_FAILED; + break; + } + addr += mtd->erasesize; + } + mutex_unlock(&bfmtd->lock); + + mtd_erase_callback(ei); + + return ret; +} + +static int bfmtd_init_mtd(struct bf_mtd *bfmtd) +{ + struct mtd_info *mtd; + struct bfmtd_info *info = NULL; + int ret, i; + + ret = bfmtd_read_sid(bfmtd); + if (ret < 0) + return ret; + + if (ret != STATUS_SID_I2CS) { + dev_err(bfmtd->dev, "Unsupported silicon id: %u", ret); + return -ENODEV; + } + + ret = bfmtd_read_did(bfmtd); + if (ret < 0) + return ret; + + for (i = 0; i < ARRAY_SIZE(bfmtd_info_db); i++) { + if (bfmtd_info_db[i].device_id == ret) { + info = &bfmtd_info_db[i]; + break; + } + } + + if (!info) { + dev_err(bfmtd->dev, "Unsupported device id: %u", ret); + return -ENODEV; + } + + ret = bfmtd_read_p2addr(bfmtd); + if (ret < 0) + return ret; + + /* Reconfigure sizes if not in "power-of-2" addressing mode */ + if (!(ret & STATUS_POWER_2_ADDR)) { + info->page_size += info->spare_size; + info->writebufsize = info->page_size; + info->flash_size = info->page_size * info->nr_pages; + info->sector_size = info->flash_size / info->nr_sectors; + } + + dev_info(bfmtd->dev, "%s configuration flash in \'%s\' addressing mode\n", + info->name, + (ret & STATUS_POWER_2_ADDR) ? "power-of-2" : "default"); + + ret = ioread8(bfmtd->base + BOOT_FPGA_RU_CONFIG_CONTROL_STATUS); + dev_info(bfmtd->dev, "active FPGA configuration: %s\n", + (ret & RU_CCS_USER_IMAGE) ? "user" : "golden"); + + bfmtd->info = info; + mtd = &bfmtd->mtd; + mtd->name = dev_name(bfmtd->dev); + mtd->type = MTD_NORFLASH; + mtd->flags = MTD_CAP_NORFLASH; + mtd->erasesize = info->page_size; + mtd->writesize = info->writesize; + mtd->writebufsize = info->writebufsize; + mtd->size = info->flash_size; + mtd->_erase = bfmtd_erase; + mtd->_read = bfmtd_read; + mtd->_write = bfmtd_write; + + return 0; +} + +static int bfmtd_probe(struct platform_device *pdev) +{ + struct mtd_part_parser_data ppdata = {}; + struct bf_mtd *bfmtd; + struct resource *mem; + struct device *dev = &pdev->dev; + int ret; + + bfmtd = devm_kzalloc(dev, sizeof(*bfmtd), GFP_KERNEL); + if (!bfmtd) + return -ENOMEM; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) { + dev_err(dev, "Failed to get platform mmio resource\n"); + return -ENOENT; + } + + bfmtd->base = devm_ioremap_nocache(dev, mem->start, resource_size(mem)); + if (IS_ERR(bfmtd->base)) { + dev_err(dev, "Failed to ioremap mmio memory\n"); + return PTR_ERR(bfmtd->base); + } + + bfmtd->dev = dev; + ret = bfmtd_init_mtd(bfmtd); + if (ret) + return ret; + + ret = mtd_device_parse_register(&bfmtd->mtd, NULL, &ppdata, NULL, 0); + if (ret) { + dev_err(dev, "Failed to register MTD device (%d)\n", ret); + return ret; + } + + mutex_init(&bfmtd->lock); + + platform_set_drvdata(pdev, bfmtd); + + return ret; +} + +static int bfmtd_remove(struct platform_device *pdev) +{ + struct bf_mtd *bfmtd = platform_get_drvdata(pdev); + + mtd_device_unregister(&bfmtd->mtd); + + return 0; +} + +static const struct of_device_id ptx1kbf_mtd_ids[] = { + { .compatible = "jnx,ptx1kbf-mtd", }, + { }, +}; +MODULE_DEVICE_TABLE(of, ptx1kbf_mtd_ids); + +static struct platform_driver bfmtd_driver = { + .probe = bfmtd_probe, + .remove = bfmtd_remove, + .driver = { + .name = "jnx-ptx1kbf-mtd", + .owner = THIS_MODULE, + .of_match_table = ptx1kbf_mtd_ids, + }, +}; + +module_platform_driver(bfmtd_driver); + +MODULE_DESCRIPTION("Juniper Networks PTX1K RCB I2CS Boot FPGA MTD driver"); +MODULE_AUTHOR("Georgi Vlaev "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:jnx-ptx1kbf-mtd");