55623a9c83
Fixes the following security vulnerabilities: CVE-2017-8890 The inet_csk_clone_lock function in net/ipv4/inet_connection_sock.c in the Linux kernel through 4.10.15 allows attackers to cause a denial of service (double free) or possibly have unspecified other impact by leveraging use of the accept system call. CVE-2017-9074 The IPv6 fragmentation implementation in the Linux kernel through 4.11.1 does not consider that the nexthdr field may be associated with an invalid option, which allows local users to cause a denial of service (out-of-bounds read and BUG) or possibly have unspecified other impact via crafted socket and send system calls. CVE-2017-9075 The sctp_v6_create_accept_sk function in net/sctp/ipv6.c in the Linux kernel through 4.11.1 mishandles inheritance, which allows local users to cause a denial of service or possibly have unspecified other impact via crafted system calls, a related issue to CVE-2017-8890. CVE-2017-9076 The dccp_v6_request_recv_sock function in net/dccp/ipv6.c in the Linux kernel through 4.11.1 mishandles inheritance, which allows local users to cause a denial of service or possibly have unspecified other impact via crafted system calls, a related issue to CVE-2017-8890. CVE-2017-9077 The tcp_v6_syn_recv_sock function in net/ipv6/tcp_ipv6.c in the Linux kernel through 4.11.1 mishandles inheritance, which allows local users to cause a denial of service or possibly have unspecified other impact via crafted system calls, a related issue to CVE-2017-8890. CVE-2017-9242 The __ip6_append_data function in net/ipv6/ip6_output.c in the Linux kernel through 4.11.3 is too late in checking whether an overwrite of an skb data structure may occur, which allows local users to cause a denial of service (system crash) via crafted system calls. Ref: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-8890 Ref: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-9074 Ref: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-9075 Ref: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-9076 Ref: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-9077 Ref: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-9242 Ref: https://www.kernel.org/pub/linux/kernel/v4.x/ChangeLog-4.9.31 Signed-off-by: Jo-Philipp Wich <jo@mein.io>
1266 lines
38 KiB
Diff
1266 lines
38 KiB
Diff
From 645c7805f2602569263d7ac78050b2c9e91e3377 Mon Sep 17 00:00:00 2001
|
|
From: Ram Chandra Jangir <rjangir@codeaurora.org>
|
|
Date: Thu, 20 Apr 2017 10:23:00 +0530
|
|
Subject: [PATCH] qcom: mtd: nand: Added bam transaction and support
|
|
additional CSRs
|
|
|
|
This patch adds the following for NAND BAM DMA support
|
|
- Bam transaction which will be used for any NAND request.
|
|
It contains the array of command elements, command and
|
|
data sgl. This transaction will be resetted before every
|
|
request.
|
|
- Allocation function for NAND BAM transaction which will be
|
|
called only once at probe time.
|
|
- Reset function for NAND BAM transaction which will be called
|
|
before any new NAND request.
|
|
- Add support for additional CSRs.
|
|
NAND_READ_LOCATION - page offset for reading in BAM DMA mode
|
|
NAND_ERASED_CW_DETECT_CFG - status for erased code words
|
|
NAND_BUFFER_STATUS - status for ECC
|
|
|
|
Signed-off-by: Abhishek Sahu <absahu@codeaurora.org>
|
|
Signed-off-by: Ram Chandra Jangir <rjangir@codeaurora.org>
|
|
---
|
|
drivers/mtd/nand/qcom_nandc.c | 631 +++++++++++++++++++++++++++++++++++----
|
|
include/linux/dma/qcom_bam_dma.h | 149 +++++++++
|
|
2 files changed, 721 insertions(+), 59 deletions(-)
|
|
create mode 100644 include/linux/dma/qcom_bam_dma.h
|
|
|
|
--- a/drivers/mtd/nand/qcom_nandc.c
|
|
+++ b/drivers/mtd/nand/qcom_nandc.c
|
|
@@ -22,6 +22,7 @@
|
|
#include <linux/of.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/delay.h>
|
|
+#include <linux/dma/qcom_bam_dma.h>
|
|
|
|
/* NANDc reg offsets */
|
|
#define NAND_FLASH_CMD 0x00
|
|
@@ -53,6 +54,8 @@
|
|
#define NAND_VERSION 0xf08
|
|
#define NAND_READ_LOCATION_0 0xf20
|
|
#define NAND_READ_LOCATION_1 0xf24
|
|
+#define NAND_READ_LOCATION_2 0xf28
|
|
+#define NAND_READ_LOCATION_3 0xf2c
|
|
|
|
/* dummy register offsets, used by write_reg_dma */
|
|
#define NAND_DEV_CMD1_RESTORE 0xdead
|
|
@@ -131,6 +134,11 @@
|
|
#define ERASED_PAGE (PAGE_ALL_ERASED | PAGE_ERASED)
|
|
#define ERASED_CW (CODEWORD_ALL_ERASED | CODEWORD_ERASED)
|
|
|
|
+/* NAND_READ_LOCATION_n bits */
|
|
+#define READ_LOCATION_OFFSET 0
|
|
+#define READ_LOCATION_SIZE 16
|
|
+#define READ_LOCATION_LAST 31
|
|
+
|
|
/* Version Mask */
|
|
#define NAND_VERSION_MAJOR_MASK 0xf0000000
|
|
#define NAND_VERSION_MAJOR_SHIFT 28
|
|
@@ -148,6 +156,9 @@
|
|
#define FETCH_ID 0xb
|
|
#define RESET_DEVICE 0xd
|
|
|
|
+/* NAND_CTRL bits */
|
|
+#define BAM_MODE_EN BIT(0)
|
|
+
|
|
/*
|
|
* the NAND controller performs reads/writes with ECC in 516 byte chunks.
|
|
* the driver calls the chunks 'step' or 'codeword' interchangeably
|
|
@@ -169,12 +180,77 @@
|
|
#define ECC_BCH_4BIT BIT(2)
|
|
#define ECC_BCH_8BIT BIT(3)
|
|
|
|
+/* Flags used for BAM DMA desc preparation*/
|
|
+/* Don't set the EOT in current tx sgl */
|
|
+#define DMA_DESC_FLAG_NO_EOT (0x0001)
|
|
+/* Set the NWD flag in current sgl */
|
|
+#define DMA_DESC_FLAG_BAM_NWD (0x0002)
|
|
+/* Close current sgl and start writing in another sgl */
|
|
+#define DMA_DESC_FLAG_BAM_NEXT_SGL (0x0004)
|
|
+/*
|
|
+ * Erased codeword status is being used two times in single transfer so this
|
|
+ * flag will determine the current value of erased codeword status register
|
|
+ */
|
|
+#define DMA_DESC_ERASED_CW_SET (0x0008)
|
|
+
|
|
+/* Returns the dma address for reg read buffer */
|
|
+#define REG_BUF_DMA_ADDR(chip, vaddr) \
|
|
+ ((chip)->reg_read_buf_phys + \
|
|
+ ((uint8_t *)(vaddr) - (uint8_t *)(chip)->reg_read_buf))
|
|
+
|
|
+/* Returns the nand register physical address */
|
|
+#define NAND_REG_PHYS_ADDRESS(chip, addr) \
|
|
+ ((chip)->base_dma + (addr))
|
|
+
|
|
+/* command element array size in bam transaction */
|
|
+#define BAM_CMD_ELEMENT_SIZE (256)
|
|
+/* command sgl size in bam transaction */
|
|
+#define BAM_CMD_SGL_SIZE (256)
|
|
+/* data sgl size in bam transaction */
|
|
+#define BAM_DATA_SGL_SIZE (128)
|
|
+
|
|
+/*
|
|
+ * This data type corresponds to the BAM transaction which will be used for any
|
|
+ * nand request.
|
|
+ * @bam_ce - the array of bam command elements
|
|
+ * @cmd_sgl - sgl for nand bam command pipe
|
|
+ * @tx_sgl - sgl for nand bam consumer pipe
|
|
+ * @rx_sgl - sgl for nand bam producer pipe
|
|
+ * @bam_ce_index - the index in bam_ce which is available for next sgl request
|
|
+ * @pre_bam_ce_index - the index in bam_ce which marks the start position ce
|
|
+ * for current sgl. It will be used for size calculation
|
|
+ * for current sgl
|
|
+ * @cmd_sgl_cnt - no of entries in command sgl.
|
|
+ * @tx_sgl_cnt - no of entries in tx sgl.
|
|
+ * @rx_sgl_cnt - no of entries in rx sgl.
|
|
+ */
|
|
+struct bam_transaction {
|
|
+ struct bam_cmd_element bam_ce[BAM_CMD_ELEMENT_SIZE];
|
|
+ struct qcom_bam_sgl cmd_sgl[BAM_CMD_SGL_SIZE];
|
|
+ struct qcom_bam_sgl tx_sgl[BAM_DATA_SGL_SIZE];
|
|
+ struct qcom_bam_sgl rx_sgl[BAM_DATA_SGL_SIZE];
|
|
+ uint32_t bam_ce_index;
|
|
+ uint32_t pre_bam_ce_index;
|
|
+ uint32_t cmd_sgl_cnt;
|
|
+ uint32_t tx_sgl_cnt;
|
|
+ uint32_t rx_sgl_cnt;
|
|
+};
|
|
+
|
|
+/**
|
|
+ * This data type corresponds to the nand dma descriptor
|
|
+ * @list - list for desc_info
|
|
+ * @dir - DMA transfer direction
|
|
+ * @sgl - sgl which will be used for single sgl dma descriptor
|
|
+ * @dma_desc - low level dma engine descriptor
|
|
+ * @bam_desc_data - used for bam desc mappings
|
|
+ */
|
|
struct desc_info {
|
|
struct list_head node;
|
|
|
|
enum dma_data_direction dir;
|
|
struct scatterlist sgl;
|
|
struct dma_async_tx_descriptor *dma_desc;
|
|
+ struct qcom_bam_custom_data bam_desc_data;
|
|
};
|
|
|
|
/*
|
|
@@ -202,6 +278,13 @@ struct nandc_regs {
|
|
__le32 orig_vld;
|
|
|
|
__le32 ecc_buf_cfg;
|
|
+ __le32 read_location0;
|
|
+ __le32 read_location1;
|
|
+ __le32 read_location2;
|
|
+ __le32 read_location3;
|
|
+
|
|
+ __le32 erased_cw_detect_cfg_clr;
|
|
+ __le32 erased_cw_detect_cfg_set;
|
|
};
|
|
|
|
/*
|
|
@@ -217,6 +300,7 @@ struct nandc_regs {
|
|
* @aon_clk: another controller clock
|
|
*
|
|
* @chan: dma channel
|
|
+ * @bam_txn: contains the bam transaction address
|
|
* @cmd_crci: ADM DMA CRCI for command flow control
|
|
* @data_crci: ADM DMA CRCI for data flow control
|
|
* @desc_list: DMA descriptor list (list of desc_infos)
|
|
@@ -242,6 +326,7 @@ struct nandc_regs {
|
|
struct qcom_nand_controller {
|
|
struct nand_hw_control controller;
|
|
struct list_head host_list;
|
|
+ struct bam_transaction *bam_txn;
|
|
|
|
struct device *dev;
|
|
|
|
@@ -342,6 +427,45 @@ struct qcom_nand_driver_data {
|
|
bool dma_bam_enabled;
|
|
};
|
|
|
|
+/* Allocates and Initializes the BAM transaction */
|
|
+struct bam_transaction *alloc_bam_transaction(
|
|
+ struct qcom_nand_controller *nandc)
|
|
+{
|
|
+ struct bam_transaction *bam_txn;
|
|
+
|
|
+ bam_txn = kzalloc(sizeof(*bam_txn), GFP_KERNEL);
|
|
+
|
|
+ if (!bam_txn)
|
|
+ return NULL;
|
|
+
|
|
+ bam_txn->bam_ce_index = 0;
|
|
+ bam_txn->pre_bam_ce_index = 0;
|
|
+ bam_txn->cmd_sgl_cnt = 0;
|
|
+ bam_txn->tx_sgl_cnt = 0;
|
|
+ bam_txn->rx_sgl_cnt = 0;
|
|
+
|
|
+ qcom_bam_sg_init_table(bam_txn->cmd_sgl, BAM_CMD_SGL_SIZE);
|
|
+ qcom_bam_sg_init_table(bam_txn->tx_sgl, BAM_DATA_SGL_SIZE);
|
|
+ qcom_bam_sg_init_table(bam_txn->rx_sgl, BAM_DATA_SGL_SIZE);
|
|
+
|
|
+ return bam_txn;
|
|
+}
|
|
+
|
|
+/* Clears the BAM transaction index */
|
|
+void clear_bam_transaction(struct qcom_nand_controller *nandc)
|
|
+{
|
|
+ struct bam_transaction *bam_txn = nandc->bam_txn;
|
|
+
|
|
+ if (!nandc->dma_bam_enabled)
|
|
+ return;
|
|
+
|
|
+ bam_txn->bam_ce_index = 0;
|
|
+ bam_txn->pre_bam_ce_index = 0;
|
|
+ bam_txn->cmd_sgl_cnt = 0;
|
|
+ bam_txn->tx_sgl_cnt = 0;
|
|
+ bam_txn->rx_sgl_cnt = 0;
|
|
+}
|
|
+
|
|
static inline struct qcom_nand_host *to_qcom_nand_host(struct nand_chip *chip)
|
|
{
|
|
return container_of(chip, struct qcom_nand_host, chip);
|
|
@@ -398,6 +522,16 @@ static __le32 *offset_to_nandc_reg(struc
|
|
return ®s->orig_vld;
|
|
case NAND_EBI2_ECC_BUF_CFG:
|
|
return ®s->ecc_buf_cfg;
|
|
+ case NAND_BUFFER_STATUS:
|
|
+ return ®s->clrreadstatus;
|
|
+ case NAND_READ_LOCATION_0:
|
|
+ return ®s->read_location0;
|
|
+ case NAND_READ_LOCATION_1:
|
|
+ return ®s->read_location1;
|
|
+ case NAND_READ_LOCATION_2:
|
|
+ return ®s->read_location2;
|
|
+ case NAND_READ_LOCATION_3:
|
|
+ return ®s->read_location3;
|
|
default:
|
|
return NULL;
|
|
}
|
|
@@ -439,7 +573,7 @@ static void update_rw_regs(struct qcom_n
|
|
{
|
|
struct nand_chip *chip = &host->chip;
|
|
struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip);
|
|
- u32 cmd, cfg0, cfg1, ecc_bch_cfg;
|
|
+ u32 cmd, cfg0, cfg1, ecc_bch_cfg, read_location0;
|
|
|
|
if (read) {
|
|
if (host->use_ecc)
|
|
@@ -456,12 +590,20 @@ static void update_rw_regs(struct qcom_n
|
|
|
|
cfg1 = host->cfg1;
|
|
ecc_bch_cfg = host->ecc_bch_cfg;
|
|
+ if (read)
|
|
+ read_location0 = (0 << READ_LOCATION_OFFSET) |
|
|
+ (host->cw_data << READ_LOCATION_SIZE) |
|
|
+ (1 << READ_LOCATION_LAST);
|
|
} else {
|
|
cfg0 = (host->cfg0_raw & ~(7U << CW_PER_PAGE)) |
|
|
(num_cw - 1) << CW_PER_PAGE;
|
|
|
|
cfg1 = host->cfg1_raw;
|
|
ecc_bch_cfg = 1 << ECC_CFG_ECC_DISABLE;
|
|
+ if (read)
|
|
+ read_location0 = (0 << READ_LOCATION_OFFSET) |
|
|
+ (host->cw_size << READ_LOCATION_SIZE) |
|
|
+ (1 << READ_LOCATION_LAST);
|
|
}
|
|
|
|
nandc_set_reg(nandc, NAND_FLASH_CMD, cmd);
|
|
@@ -472,8 +614,104 @@ static void update_rw_regs(struct qcom_n
|
|
nandc_set_reg(nandc, NAND_FLASH_STATUS, host->clrflashstatus);
|
|
nandc_set_reg(nandc, NAND_READ_STATUS, host->clrreadstatus);
|
|
nandc_set_reg(nandc, NAND_EXEC_CMD, 1);
|
|
+
|
|
+ if (read)
|
|
+ nandc_set_reg(nandc, NAND_READ_LOCATION_0, read_location0);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Prepares the command descriptor for BAM DMA which will be used for NAND
|
|
+ * register read and write. The command descriptor requires the command
|
|
+ * to be formed in command element type so this function uses the command
|
|
+ * element from bam transaction ce array and fills the same with required
|
|
+ * data. A single SGL can contain multiple command elements so
|
|
+ * DMA_DESC_FLAG_BAM_NEXT_SGL will be used for starting the separate SGL
|
|
+ * after the current command element.
|
|
+ */
|
|
+static int prep_dma_desc_command(struct qcom_nand_controller *nandc, bool read,
|
|
+ int reg_off, const void *vaddr,
|
|
+ int size, unsigned int flags)
|
|
+{
|
|
+ int bam_ce_size;
|
|
+ int i;
|
|
+ struct bam_cmd_element *bam_ce_buffer;
|
|
+ struct bam_transaction *bam_txn = nandc->bam_txn;
|
|
+
|
|
+ bam_ce_buffer = &bam_txn->bam_ce[bam_txn->bam_ce_index];
|
|
+
|
|
+ /* fill the command desc */
|
|
+ for (i = 0; i < size; i++) {
|
|
+ if (read) {
|
|
+ qcom_prep_bam_ce(&bam_ce_buffer[i],
|
|
+ NAND_REG_PHYS_ADDRESS(nandc, reg_off + 4 * i),
|
|
+ BAM_READ_COMMAND,
|
|
+ REG_BUF_DMA_ADDR(nandc,
|
|
+ (unsigned int *)vaddr + i));
|
|
+ } else {
|
|
+ qcom_prep_bam_ce(&bam_ce_buffer[i],
|
|
+ NAND_REG_PHYS_ADDRESS(nandc, reg_off + 4 * i),
|
|
+ BAM_WRITE_COMMAND,
|
|
+ *((unsigned int *)vaddr + i));
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* use the separate sgl after this command */
|
|
+ if (flags & DMA_DESC_FLAG_BAM_NEXT_SGL) {
|
|
+ bam_ce_buffer = &bam_txn->bam_ce[bam_txn->pre_bam_ce_index];
|
|
+ bam_txn->bam_ce_index += size;
|
|
+ bam_ce_size = (bam_txn->bam_ce_index -
|
|
+ bam_txn->pre_bam_ce_index) *
|
|
+ sizeof(struct bam_cmd_element);
|
|
+ sg_set_buf(&bam_txn->cmd_sgl[bam_txn->cmd_sgl_cnt].sgl,
|
|
+ bam_ce_buffer,
|
|
+ bam_ce_size);
|
|
+ if (flags & DMA_DESC_FLAG_BAM_NWD)
|
|
+ bam_txn->cmd_sgl[bam_txn->cmd_sgl_cnt].dma_flags =
|
|
+ DESC_FLAG_NWD | DESC_FLAG_CMD;
|
|
+ else
|
|
+ bam_txn->cmd_sgl[bam_txn->cmd_sgl_cnt].dma_flags =
|
|
+ DESC_FLAG_CMD;
|
|
+
|
|
+ bam_txn->cmd_sgl_cnt++;
|
|
+ bam_txn->pre_bam_ce_index = bam_txn->bam_ce_index;
|
|
+ } else {
|
|
+ bam_txn->bam_ce_index += size;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
}
|
|
|
|
+/*
|
|
+ * Prepares the data descriptor for BAM DMA which will be used for NAND
|
|
+ * data read and write.
|
|
+ */
|
|
+static int prep_dma_desc_data_bam(struct qcom_nand_controller *nandc, bool read,
|
|
+ int reg_off, const void *vaddr,
|
|
+ int size, unsigned int flags)
|
|
+{
|
|
+ struct bam_transaction *bam_txn = nandc->bam_txn;
|
|
+
|
|
+ if (read) {
|
|
+ sg_set_buf(&bam_txn->rx_sgl[bam_txn->rx_sgl_cnt].sgl,
|
|
+ vaddr, size);
|
|
+ bam_txn->rx_sgl[bam_txn->rx_sgl_cnt].dma_flags = 0;
|
|
+ bam_txn->rx_sgl_cnt++;
|
|
+ } else {
|
|
+ sg_set_buf(&bam_txn->tx_sgl[bam_txn->tx_sgl_cnt].sgl,
|
|
+ vaddr, size);
|
|
+ if (flags & DMA_DESC_FLAG_NO_EOT)
|
|
+ bam_txn->tx_sgl[bam_txn->tx_sgl_cnt].dma_flags = 0;
|
|
+ else
|
|
+ bam_txn->tx_sgl[bam_txn->tx_sgl_cnt].dma_flags =
|
|
+ DESC_FLAG_EOT;
|
|
+
|
|
+ bam_txn->tx_sgl_cnt++;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* Prepares the dma desciptor for adm dma engine */
|
|
static int prep_dma_desc(struct qcom_nand_controller *nandc, bool read,
|
|
int reg_off, const void *vaddr, int size,
|
|
bool flow_control)
|
|
@@ -552,7 +790,7 @@ err:
|
|
* @num_regs: number of registers to read
|
|
*/
|
|
static int read_reg_dma(struct qcom_nand_controller *nandc, int first,
|
|
- int num_regs)
|
|
+ int num_regs, unsigned int flags)
|
|
{
|
|
bool flow_control = false;
|
|
void *vaddr;
|
|
@@ -561,10 +799,18 @@ static int read_reg_dma(struct qcom_nand
|
|
if (first == NAND_READ_ID || first == NAND_FLASH_STATUS)
|
|
flow_control = true;
|
|
|
|
- size = num_regs * sizeof(u32);
|
|
vaddr = nandc->reg_read_buf + nandc->reg_read_pos;
|
|
nandc->reg_read_pos += num_regs;
|
|
|
|
+ if (nandc->dma_bam_enabled) {
|
|
+ size = num_regs;
|
|
+
|
|
+ return prep_dma_desc_command(nandc, true, first, vaddr, size,
|
|
+ flags);
|
|
+ }
|
|
+
|
|
+ size = num_regs * sizeof(u32);
|
|
+
|
|
return prep_dma_desc(nandc, true, first, vaddr, size, flow_control);
|
|
}
|
|
|
|
@@ -576,7 +822,7 @@ static int read_reg_dma(struct qcom_nand
|
|
* @num_regs: number of registers to write
|
|
*/
|
|
static int write_reg_dma(struct qcom_nand_controller *nandc, int first,
|
|
- int num_regs)
|
|
+ int num_regs, unsigned int flags)
|
|
{
|
|
bool flow_control = false;
|
|
struct nandc_regs *regs = nandc->regs;
|
|
@@ -588,12 +834,29 @@ static int write_reg_dma(struct qcom_nan
|
|
if (first == NAND_FLASH_CMD)
|
|
flow_control = true;
|
|
|
|
+ if (first == NAND_ERASED_CW_DETECT_CFG) {
|
|
+ if (flags & DMA_DESC_ERASED_CW_SET)
|
|
+ vaddr = ®s->erased_cw_detect_cfg_set;
|
|
+ else
|
|
+ vaddr = ®s->erased_cw_detect_cfg_clr;
|
|
+ }
|
|
+
|
|
+ if (first == NAND_EXEC_CMD)
|
|
+ flags |= DMA_DESC_FLAG_BAM_NWD;
|
|
+
|
|
if (first == NAND_DEV_CMD1_RESTORE)
|
|
first = NAND_DEV_CMD1;
|
|
|
|
if (first == NAND_DEV_CMD_VLD_RESTORE)
|
|
first = NAND_DEV_CMD_VLD;
|
|
|
|
+ if (nandc->dma_bam_enabled) {
|
|
+ size = num_regs;
|
|
+
|
|
+ return prep_dma_desc_command(nandc, false, first, vaddr, size,
|
|
+ flags);
|
|
+ }
|
|
+
|
|
size = num_regs * sizeof(u32);
|
|
|
|
return prep_dma_desc(nandc, false, first, vaddr, size, flow_control);
|
|
@@ -608,8 +871,12 @@ static int write_reg_dma(struct qcom_nan
|
|
* @size: DMA transaction size in bytes
|
|
*/
|
|
static int read_data_dma(struct qcom_nand_controller *nandc, int reg_off,
|
|
- const u8 *vaddr, int size)
|
|
+ const u8 *vaddr, int size, unsigned int flags)
|
|
{
|
|
+ if (nandc->dma_bam_enabled)
|
|
+ return prep_dma_desc_data_bam(nandc, true, reg_off, vaddr, size,
|
|
+ flags);
|
|
+
|
|
return prep_dma_desc(nandc, true, reg_off, vaddr, size, false);
|
|
}
|
|
|
|
@@ -622,8 +889,12 @@ static int read_data_dma(struct qcom_nan
|
|
* @size: DMA transaction size in bytes
|
|
*/
|
|
static int write_data_dma(struct qcom_nand_controller *nandc, int reg_off,
|
|
- const u8 *vaddr, int size)
|
|
+ const u8 *vaddr, int size, unsigned int flags)
|
|
{
|
|
+ if (nandc->dma_bam_enabled)
|
|
+ return prep_dma_desc_data_bam(nandc, false, reg_off, vaddr,
|
|
+ size, flags);
|
|
+
|
|
return prep_dma_desc(nandc, false, reg_off, vaddr, size, false);
|
|
}
|
|
|
|
@@ -633,14 +904,57 @@ static int write_data_dma(struct qcom_na
|
|
*/
|
|
static void config_cw_read(struct qcom_nand_controller *nandc)
|
|
{
|
|
- write_reg_dma(nandc, NAND_FLASH_CMD, 3);
|
|
- write_reg_dma(nandc, NAND_DEV0_CFG0, 3);
|
|
- write_reg_dma(nandc, NAND_EBI2_ECC_BUF_CFG, 1);
|
|
|
|
- write_reg_dma(nandc, NAND_EXEC_CMD, 1);
|
|
+ write_reg_dma(nandc, NAND_FLASH_CMD, 3, 0);
|
|
+ write_reg_dma(nandc, NAND_DEV0_CFG0, 3, 0);
|
|
+ write_reg_dma(nandc, NAND_EBI2_ECC_BUF_CFG, 1, 0);
|
|
+
|
|
+ write_reg_dma(nandc, NAND_ERASED_CW_DETECT_CFG, 1, 0);
|
|
+ write_reg_dma(nandc, NAND_ERASED_CW_DETECT_CFG, 1,
|
|
+ DMA_DESC_ERASED_CW_SET);
|
|
+ if (nandc->dma_bam_enabled)
|
|
+ write_reg_dma(nandc, NAND_READ_LOCATION_0, 1,
|
|
+ DMA_DESC_FLAG_BAM_NEXT_SGL);
|
|
|
|
- read_reg_dma(nandc, NAND_FLASH_STATUS, 2);
|
|
- read_reg_dma(nandc, NAND_ERASED_CW_DETECT_STATUS, 1);
|
|
+
|
|
+ write_reg_dma(nandc, NAND_EXEC_CMD, 1, DMA_DESC_FLAG_BAM_NWD |
|
|
+ DMA_DESC_FLAG_BAM_NEXT_SGL);
|
|
+
|
|
+ read_reg_dma(nandc, NAND_FLASH_STATUS, 2, 0);
|
|
+ read_reg_dma(nandc, NAND_ERASED_CW_DETECT_STATUS, 1,
|
|
+ DMA_DESC_FLAG_BAM_NEXT_SGL);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Helpers to prepare DMA descriptors for configuring registers
|
|
+ * before reading a NAND page with BAM.
|
|
+ */
|
|
+static void config_bam_page_read(struct qcom_nand_controller *nandc)
|
|
+{
|
|
+ write_reg_dma(nandc, NAND_FLASH_CMD, 3, 0);
|
|
+ write_reg_dma(nandc, NAND_DEV0_CFG0, 3, 0);
|
|
+ write_reg_dma(nandc, NAND_EBI2_ECC_BUF_CFG, 1, 0);
|
|
+ write_reg_dma(nandc, NAND_ERASED_CW_DETECT_CFG, 1, 0);
|
|
+ write_reg_dma(nandc, NAND_ERASED_CW_DETECT_CFG, 1,
|
|
+ DMA_DESC_ERASED_CW_SET |
|
|
+ DMA_DESC_FLAG_BAM_NEXT_SGL);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Helpers to prepare DMA descriptors for configuring registers
|
|
+ * before reading each codeword in NAND page with BAM.
|
|
+ */
|
|
+static void config_bam_cw_read(struct qcom_nand_controller *nandc)
|
|
+{
|
|
+ if (nandc->dma_bam_enabled)
|
|
+ write_reg_dma(nandc, NAND_READ_LOCATION_0, 4, 0);
|
|
+
|
|
+ write_reg_dma(nandc, NAND_FLASH_CMD, 1, DMA_DESC_FLAG_BAM_NEXT_SGL);
|
|
+ write_reg_dma(nandc, NAND_EXEC_CMD, 1, DMA_DESC_FLAG_BAM_NEXT_SGL);
|
|
+
|
|
+ read_reg_dma(nandc, NAND_FLASH_STATUS, 2, 0);
|
|
+ read_reg_dma(nandc, NAND_ERASED_CW_DETECT_STATUS, 1,
|
|
+ DMA_DESC_FLAG_BAM_NEXT_SGL);
|
|
}
|
|
|
|
/*
|
|
@@ -649,19 +963,20 @@ static void config_cw_read(struct qcom_n
|
|
*/
|
|
static void config_cw_write_pre(struct qcom_nand_controller *nandc)
|
|
{
|
|
- write_reg_dma(nandc, NAND_FLASH_CMD, 3);
|
|
- write_reg_dma(nandc, NAND_DEV0_CFG0, 3);
|
|
- write_reg_dma(nandc, NAND_EBI2_ECC_BUF_CFG, 1);
|
|
+ write_reg_dma(nandc, NAND_FLASH_CMD, 3, 0);
|
|
+ write_reg_dma(nandc, NAND_DEV0_CFG0, 3, 0);
|
|
+ write_reg_dma(nandc, NAND_EBI2_ECC_BUF_CFG, 1,
|
|
+ DMA_DESC_FLAG_BAM_NEXT_SGL);
|
|
}
|
|
|
|
static void config_cw_write_post(struct qcom_nand_controller *nandc)
|
|
{
|
|
- write_reg_dma(nandc, NAND_EXEC_CMD, 1);
|
|
+ write_reg_dma(nandc, NAND_EXEC_CMD, 1, DMA_DESC_FLAG_BAM_NEXT_SGL);
|
|
|
|
- read_reg_dma(nandc, NAND_FLASH_STATUS, 1);
|
|
+ read_reg_dma(nandc, NAND_FLASH_STATUS, 1, DMA_DESC_FLAG_BAM_NEXT_SGL);
|
|
|
|
- write_reg_dma(nandc, NAND_FLASH_STATUS, 1);
|
|
- write_reg_dma(nandc, NAND_READ_STATUS, 1);
|
|
+ write_reg_dma(nandc, NAND_FLASH_STATUS, 1, 0);
|
|
+ write_reg_dma(nandc, NAND_READ_STATUS, 1, DMA_DESC_FLAG_BAM_NEXT_SGL);
|
|
}
|
|
|
|
/*
|
|
@@ -675,6 +990,8 @@ static int nandc_param(struct qcom_nand_
|
|
struct nand_chip *chip = &host->chip;
|
|
struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip);
|
|
|
|
+ clear_bam_transaction(nandc);
|
|
+
|
|
/*
|
|
* NAND_CMD_PARAM is called before we know much about the FLASH chip
|
|
* in use. we configure the controller to perform a raw read of 512
|
|
@@ -708,9 +1025,13 @@ static int nandc_param(struct qcom_nand_
|
|
|
|
nandc_set_reg(nandc, NAND_DEV_CMD1_RESTORE, nandc->cmd1);
|
|
nandc_set_reg(nandc, NAND_DEV_CMD_VLD_RESTORE, nandc->vld);
|
|
+ nandc_set_reg(nandc, NAND_READ_LOCATION_0,
|
|
+ (0 << READ_LOCATION_OFFSET) |
|
|
+ (512 << READ_LOCATION_SIZE) |
|
|
+ (1 << READ_LOCATION_LAST));
|
|
|
|
- write_reg_dma(nandc, NAND_DEV_CMD_VLD, 1);
|
|
- write_reg_dma(nandc, NAND_DEV_CMD1, 1);
|
|
+ write_reg_dma(nandc, NAND_DEV_CMD_VLD, 1, 0);
|
|
+ write_reg_dma(nandc, NAND_DEV_CMD1, 1, DMA_DESC_FLAG_BAM_NEXT_SGL);
|
|
|
|
nandc->buf_count = 512;
|
|
memset(nandc->data_buffer, 0xff, nandc->buf_count);
|
|
@@ -718,11 +1039,12 @@ static int nandc_param(struct qcom_nand_
|
|
config_cw_read(nandc);
|
|
|
|
read_data_dma(nandc, FLASH_BUF_ACC, nandc->data_buffer,
|
|
- nandc->buf_count);
|
|
+ nandc->buf_count, 0);
|
|
|
|
/* restore CMD1 and VLD regs */
|
|
- write_reg_dma(nandc, NAND_DEV_CMD1_RESTORE, 1);
|
|
- write_reg_dma(nandc, NAND_DEV_CMD_VLD_RESTORE, 1);
|
|
+ write_reg_dma(nandc, NAND_DEV_CMD1_RESTORE, 1, 0);
|
|
+ write_reg_dma(nandc, NAND_DEV_CMD_VLD_RESTORE, 1,
|
|
+ DMA_DESC_FLAG_BAM_NEXT_SGL);
|
|
|
|
return 0;
|
|
}
|
|
@@ -733,6 +1055,8 @@ static int erase_block(struct qcom_nand_
|
|
struct nand_chip *chip = &host->chip;
|
|
struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip);
|
|
|
|
+ clear_bam_transaction(nandc);
|
|
+
|
|
nandc_set_reg(nandc, NAND_FLASH_CMD,
|
|
BLOCK_ERASE | PAGE_ACC | LAST_PAGE);
|
|
nandc_set_reg(nandc, NAND_ADDR0, page_addr);
|
|
@@ -744,14 +1068,15 @@ static int erase_block(struct qcom_nand_
|
|
nandc_set_reg(nandc, NAND_FLASH_STATUS, host->clrflashstatus);
|
|
nandc_set_reg(nandc, NAND_READ_STATUS, host->clrreadstatus);
|
|
|
|
- write_reg_dma(nandc, NAND_FLASH_CMD, 3);
|
|
- write_reg_dma(nandc, NAND_DEV0_CFG0, 2);
|
|
- write_reg_dma(nandc, NAND_EXEC_CMD, 1);
|
|
|
|
- read_reg_dma(nandc, NAND_FLASH_STATUS, 1);
|
|
+ write_reg_dma(nandc, NAND_FLASH_CMD, 3, DMA_DESC_FLAG_BAM_NEXT_SGL);
|
|
+ write_reg_dma(nandc, NAND_DEV0_CFG0, 2, DMA_DESC_FLAG_BAM_NEXT_SGL);
|
|
+ write_reg_dma(nandc, NAND_EXEC_CMD, 1, DMA_DESC_FLAG_BAM_NEXT_SGL);
|
|
+
|
|
+ read_reg_dma(nandc, NAND_FLASH_STATUS, 1, DMA_DESC_FLAG_BAM_NEXT_SGL);
|
|
|
|
- write_reg_dma(nandc, NAND_FLASH_STATUS, 1);
|
|
- write_reg_dma(nandc, NAND_READ_STATUS, 1);
|
|
+ write_reg_dma(nandc, NAND_FLASH_STATUS, 1, 0);
|
|
+ write_reg_dma(nandc, NAND_READ_STATUS, 1, DMA_DESC_FLAG_BAM_NEXT_SGL);
|
|
|
|
return 0;
|
|
}
|
|
@@ -765,16 +1090,19 @@ static int read_id(struct qcom_nand_host
|
|
if (column == -1)
|
|
return 0;
|
|
|
|
+ clear_bam_transaction(nandc);
|
|
+
|
|
nandc_set_reg(nandc, NAND_FLASH_CMD, FETCH_ID);
|
|
nandc_set_reg(nandc, NAND_ADDR0, column);
|
|
nandc_set_reg(nandc, NAND_ADDR1, 0);
|
|
- nandc_set_reg(nandc, NAND_FLASH_CHIP_SELECT, DM_EN);
|
|
+ nandc_set_reg(nandc, NAND_FLASH_CHIP_SELECT,
|
|
+ nandc->dma_bam_enabled ? 0 : DM_EN);
|
|
nandc_set_reg(nandc, NAND_EXEC_CMD, 1);
|
|
|
|
- write_reg_dma(nandc, NAND_FLASH_CMD, 4);
|
|
- write_reg_dma(nandc, NAND_EXEC_CMD, 1);
|
|
+ write_reg_dma(nandc, NAND_FLASH_CMD, 4, DMA_DESC_FLAG_BAM_NEXT_SGL);
|
|
+ write_reg_dma(nandc, NAND_EXEC_CMD, 1, DMA_DESC_FLAG_BAM_NEXT_SGL);
|
|
|
|
- read_reg_dma(nandc, NAND_READ_ID, 1);
|
|
+ read_reg_dma(nandc, NAND_READ_ID, 1, DMA_DESC_FLAG_BAM_NEXT_SGL);
|
|
|
|
return 0;
|
|
}
|
|
@@ -785,28 +1113,108 @@ static int reset(struct qcom_nand_host *
|
|
struct nand_chip *chip = &host->chip;
|
|
struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip);
|
|
|
|
+ clear_bam_transaction(nandc);
|
|
+
|
|
nandc_set_reg(nandc, NAND_FLASH_CMD, RESET_DEVICE);
|
|
nandc_set_reg(nandc, NAND_EXEC_CMD, 1);
|
|
|
|
- write_reg_dma(nandc, NAND_FLASH_CMD, 1);
|
|
- write_reg_dma(nandc, NAND_EXEC_CMD, 1);
|
|
+ write_reg_dma(nandc, NAND_FLASH_CMD, 1, DMA_DESC_FLAG_BAM_NEXT_SGL);
|
|
+ write_reg_dma(nandc, NAND_EXEC_CMD, 1, DMA_DESC_FLAG_BAM_NEXT_SGL);
|
|
|
|
- read_reg_dma(nandc, NAND_FLASH_STATUS, 1);
|
|
+ read_reg_dma(nandc, NAND_FLASH_STATUS, 1, DMA_DESC_FLAG_BAM_NEXT_SGL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
+static int prepare_bam_async_desc(struct qcom_nand_controller *nandc,
|
|
+ struct dma_chan *chan,
|
|
+ struct qcom_bam_sgl *bam_sgl,
|
|
+ int sgl_cnt,
|
|
+ enum dma_transfer_direction direction)
|
|
+{
|
|
+ struct desc_info *desc;
|
|
+ struct dma_async_tx_descriptor *dma_desc;
|
|
+
|
|
+ if (!qcom_bam_map_sg(nandc->dev, bam_sgl, sgl_cnt, direction)) {
|
|
+ dev_err(nandc->dev, "failure in mapping sgl\n");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ desc = kzalloc(sizeof(*desc), GFP_KERNEL);
|
|
+ if (!desc) {
|
|
+ qcom_bam_unmap_sg(nandc->dev, bam_sgl, sgl_cnt, direction);
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+
|
|
+ desc->bam_desc_data.dir = direction;
|
|
+ desc->bam_desc_data.sgl_cnt = sgl_cnt;
|
|
+ desc->bam_desc_data.bam_sgl = bam_sgl;
|
|
+
|
|
+ dma_desc = dmaengine_prep_dma_custom_mapping(chan,
|
|
+ &desc->bam_desc_data,
|
|
+ 0);
|
|
+
|
|
+ if (!dma_desc) {
|
|
+ dev_err(nandc->dev, "failure in cmd prep desc\n");
|
|
+ qcom_bam_unmap_sg(nandc->dev, bam_sgl, sgl_cnt, direction);
|
|
+ kfree(desc);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ desc->dma_desc = dma_desc;
|
|
+
|
|
+ list_add_tail(&desc->node, &nandc->desc_list);
|
|
+
|
|
+ return 0;
|
|
+
|
|
+}
|
|
+
|
|
/* helpers to submit/free our list of dma descriptors */
|
|
static int submit_descs(struct qcom_nand_controller *nandc)
|
|
{
|
|
struct desc_info *desc;
|
|
dma_cookie_t cookie = 0;
|
|
+ struct bam_transaction *bam_txn = nandc->bam_txn;
|
|
+ int r;
|
|
+
|
|
+ if (nandc->dma_bam_enabled) {
|
|
+ if (bam_txn->rx_sgl_cnt) {
|
|
+ r = prepare_bam_async_desc(nandc, nandc->rx_chan,
|
|
+ bam_txn->rx_sgl, bam_txn->rx_sgl_cnt,
|
|
+ DMA_DEV_TO_MEM);
|
|
+ if (r)
|
|
+ return r;
|
|
+ }
|
|
+
|
|
+ if (bam_txn->tx_sgl_cnt) {
|
|
+ r = prepare_bam_async_desc(nandc, nandc->tx_chan,
|
|
+ bam_txn->tx_sgl, bam_txn->tx_sgl_cnt,
|
|
+ DMA_MEM_TO_DEV);
|
|
+ if (r)
|
|
+ return r;
|
|
+ }
|
|
+
|
|
+ r = prepare_bam_async_desc(nandc, nandc->cmd_chan,
|
|
+ bam_txn->cmd_sgl, bam_txn->cmd_sgl_cnt,
|
|
+ DMA_MEM_TO_DEV);
|
|
+ if (r)
|
|
+ return r;
|
|
+ }
|
|
|
|
list_for_each_entry(desc, &nandc->desc_list, node)
|
|
cookie = dmaengine_submit(desc->dma_desc);
|
|
|
|
- if (dma_sync_wait(nandc->chan, cookie) != DMA_COMPLETE)
|
|
- return -ETIMEDOUT;
|
|
+ if (nandc->dma_bam_enabled) {
|
|
+ dma_async_issue_pending(nandc->tx_chan);
|
|
+ dma_async_issue_pending(nandc->rx_chan);
|
|
+
|
|
+ if (dma_sync_wait(nandc->cmd_chan, cookie) != DMA_COMPLETE)
|
|
+ return -ETIMEDOUT;
|
|
+ } else {
|
|
+ if (dma_sync_wait(nandc->chan, cookie) != DMA_COMPLETE)
|
|
+ return -ETIMEDOUT;
|
|
+ }
|
|
|
|
return 0;
|
|
}
|
|
@@ -817,7 +1225,16 @@ static void free_descs(struct qcom_nand_
|
|
|
|
list_for_each_entry_safe(desc, n, &nandc->desc_list, node) {
|
|
list_del(&desc->node);
|
|
- dma_unmap_sg(nandc->dev, &desc->sgl, 1, desc->dir);
|
|
+
|
|
+ if (nandc->dma_bam_enabled)
|
|
+ qcom_bam_unmap_sg(nandc->dev,
|
|
+ desc->bam_desc_data.bam_sgl,
|
|
+ desc->bam_desc_data.sgl_cnt,
|
|
+ desc->bam_desc_data.dir);
|
|
+ else
|
|
+ dma_unmap_sg(nandc->dev, &desc->sgl, 1,
|
|
+ desc->dir);
|
|
+
|
|
kfree(desc);
|
|
}
|
|
}
|
|
@@ -1128,6 +1545,9 @@ static int read_page_ecc(struct qcom_nan
|
|
struct nand_ecc_ctrl *ecc = &chip->ecc;
|
|
int i, ret;
|
|
|
|
+ if (nandc->dma_bam_enabled)
|
|
+ config_bam_page_read(nandc);
|
|
+
|
|
/* queue cmd descs for each codeword */
|
|
for (i = 0; i < ecc->steps; i++) {
|
|
int data_size, oob_size;
|
|
@@ -1141,11 +1561,36 @@ static int read_page_ecc(struct qcom_nan
|
|
oob_size = host->ecc_bytes_hw + host->spare_bytes;
|
|
}
|
|
|
|
- config_cw_read(nandc);
|
|
+ if (nandc->dma_bam_enabled) {
|
|
+ if (data_buf && oob_buf) {
|
|
+ nandc_set_reg(nandc, NAND_READ_LOCATION_0,
|
|
+ (0 << READ_LOCATION_OFFSET) |
|
|
+ (data_size << READ_LOCATION_SIZE) |
|
|
+ (0 << READ_LOCATION_LAST));
|
|
+ nandc_set_reg(nandc, NAND_READ_LOCATION_1,
|
|
+ (data_size << READ_LOCATION_OFFSET) |
|
|
+ (oob_size << READ_LOCATION_SIZE) |
|
|
+ (1 << READ_LOCATION_LAST));
|
|
+ } else if (data_buf) {
|
|
+ nandc_set_reg(nandc, NAND_READ_LOCATION_0,
|
|
+ (0 << READ_LOCATION_OFFSET) |
|
|
+ (data_size << READ_LOCATION_SIZE) |
|
|
+ (1 << READ_LOCATION_LAST));
|
|
+ } else {
|
|
+ nandc_set_reg(nandc, NAND_READ_LOCATION_0,
|
|
+ (data_size << READ_LOCATION_OFFSET) |
|
|
+ (oob_size << READ_LOCATION_SIZE) |
|
|
+ (1 << READ_LOCATION_LAST));
|
|
+ }
|
|
+
|
|
+ config_bam_cw_read(nandc);
|
|
+ } else {
|
|
+ config_cw_read(nandc);
|
|
+ }
|
|
|
|
if (data_buf)
|
|
read_data_dma(nandc, FLASH_BUF_ACC, data_buf,
|
|
- data_size);
|
|
+ data_size, 0);
|
|
|
|
/*
|
|
* when ecc is enabled, the controller doesn't read the real
|
|
@@ -1161,7 +1606,7 @@ static int read_page_ecc(struct qcom_nan
|
|
*oob_buf++ = 0xff;
|
|
|
|
read_data_dma(nandc, FLASH_BUF_ACC + data_size,
|
|
- oob_buf, oob_size);
|
|
+ oob_buf, oob_size, 0);
|
|
}
|
|
|
|
if (data_buf)
|
|
@@ -1200,10 +1645,14 @@ static int copy_last_cw(struct qcom_nand
|
|
|
|
set_address(host, host->cw_size * (ecc->steps - 1), page);
|
|
update_rw_regs(host, 1, true);
|
|
+ nandc_set_reg(nandc, NAND_READ_LOCATION_0,
|
|
+ (0 << READ_LOCATION_OFFSET) |
|
|
+ (size << READ_LOCATION_SIZE) |
|
|
+ (1 << READ_LOCATION_LAST));
|
|
|
|
config_cw_read(nandc);
|
|
|
|
- read_data_dma(nandc, FLASH_BUF_ACC, nandc->data_buffer, size);
|
|
+ read_data_dma(nandc, FLASH_BUF_ACC, nandc->data_buffer, size, 0);
|
|
|
|
ret = submit_descs(nandc);
|
|
if (ret)
|
|
@@ -1226,6 +1675,7 @@ static int qcom_nandc_read_page(struct m
|
|
data_buf = buf;
|
|
oob_buf = oob_required ? chip->oob_poi : NULL;
|
|
|
|
+ clear_bam_transaction(nandc);
|
|
ret = read_page_ecc(host, data_buf, oob_buf);
|
|
if (ret) {
|
|
dev_err(nandc->dev, "failure to read page\n");
|
|
@@ -1245,13 +1695,19 @@ static int qcom_nandc_read_page_raw(stru
|
|
u8 *data_buf, *oob_buf;
|
|
struct nand_ecc_ctrl *ecc = &chip->ecc;
|
|
int i, ret;
|
|
+ int read_location;
|
|
|
|
data_buf = buf;
|
|
oob_buf = chip->oob_poi;
|
|
|
|
host->use_ecc = false;
|
|
+
|
|
+ clear_bam_transaction(nandc);
|
|
update_rw_regs(host, ecc->steps, true);
|
|
|
|
+ if (nandc->dma_bam_enabled)
|
|
+ config_bam_page_read(nandc);
|
|
+
|
|
for (i = 0; i < ecc->steps; i++) {
|
|
int data_size1, data_size2, oob_size1, oob_size2;
|
|
int reg_off = FLASH_BUF_ACC;
|
|
@@ -1269,21 +1725,49 @@ static int qcom_nandc_read_page_raw(stru
|
|
oob_size2 = host->ecc_bytes_hw + host->spare_bytes;
|
|
}
|
|
|
|
- config_cw_read(nandc);
|
|
+ if (nandc->dma_bam_enabled) {
|
|
+ read_location = 0;
|
|
+ nandc_set_reg(nandc, NAND_READ_LOCATION_0,
|
|
+ (read_location << READ_LOCATION_OFFSET) |
|
|
+ (data_size1 << READ_LOCATION_SIZE) |
|
|
+ (0 << READ_LOCATION_LAST));
|
|
+ read_location += data_size1;
|
|
+
|
|
+ nandc_set_reg(nandc, NAND_READ_LOCATION_1,
|
|
+ (read_location << READ_LOCATION_OFFSET) |
|
|
+ (oob_size1 << READ_LOCATION_SIZE) |
|
|
+ (0 << READ_LOCATION_LAST));
|
|
+ read_location += oob_size1;
|
|
+
|
|
+ nandc_set_reg(nandc, NAND_READ_LOCATION_2,
|
|
+ (read_location << READ_LOCATION_OFFSET) |
|
|
+ (data_size2 << READ_LOCATION_SIZE) |
|
|
+ (0 << READ_LOCATION_LAST));
|
|
+ read_location += data_size2;
|
|
+
|
|
+ nandc_set_reg(nandc, NAND_READ_LOCATION_3,
|
|
+ (read_location << READ_LOCATION_OFFSET) |
|
|
+ (oob_size2 << READ_LOCATION_SIZE) |
|
|
+ (1 << READ_LOCATION_LAST));
|
|
+
|
|
+ config_bam_cw_read(nandc);
|
|
+ } else {
|
|
+ config_cw_read(nandc);
|
|
+ }
|
|
|
|
- read_data_dma(nandc, reg_off, data_buf, data_size1);
|
|
+ read_data_dma(nandc, reg_off, data_buf, data_size1, 0);
|
|
reg_off += data_size1;
|
|
data_buf += data_size1;
|
|
|
|
- read_data_dma(nandc, reg_off, oob_buf, oob_size1);
|
|
+ read_data_dma(nandc, reg_off, oob_buf, oob_size1, 0);
|
|
reg_off += oob_size1;
|
|
oob_buf += oob_size1;
|
|
|
|
- read_data_dma(nandc, reg_off, data_buf, data_size2);
|
|
+ read_data_dma(nandc, reg_off, data_buf, data_size2, 0);
|
|
reg_off += data_size2;
|
|
data_buf += data_size2;
|
|
|
|
- read_data_dma(nandc, reg_off, oob_buf, oob_size2);
|
|
+ read_data_dma(nandc, reg_off, oob_buf, oob_size2, 0);
|
|
oob_buf += oob_size2;
|
|
}
|
|
|
|
@@ -1306,6 +1790,7 @@ static int qcom_nandc_read_oob(struct mt
|
|
int ret;
|
|
|
|
clear_read_regs(nandc);
|
|
+ clear_bam_transaction(nandc);
|
|
|
|
host->use_ecc = true;
|
|
set_address(host, 0, page);
|
|
@@ -1329,6 +1814,7 @@ static int qcom_nandc_write_page(struct
|
|
int i, ret;
|
|
|
|
clear_read_regs(nandc);
|
|
+ clear_bam_transaction(nandc);
|
|
|
|
data_buf = (u8 *)buf;
|
|
oob_buf = chip->oob_poi;
|
|
@@ -1350,7 +1836,8 @@ static int qcom_nandc_write_page(struct
|
|
|
|
config_cw_write_pre(nandc);
|
|
|
|
- write_data_dma(nandc, FLASH_BUF_ACC, data_buf, data_size);
|
|
+ write_data_dma(nandc, FLASH_BUF_ACC, data_buf, data_size,
|
|
+ i == (ecc->steps - 1) ? DMA_DESC_FLAG_NO_EOT : 0);
|
|
|
|
/*
|
|
* when ECC is enabled, we don't really need to write anything
|
|
@@ -1363,7 +1850,7 @@ static int qcom_nandc_write_page(struct
|
|
oob_buf += host->bbm_size;
|
|
|
|
write_data_dma(nandc, FLASH_BUF_ACC + data_size,
|
|
- oob_buf, oob_size);
|
|
+ oob_buf, oob_size, 0);
|
|
}
|
|
|
|
config_cw_write_post(nandc);
|
|
@@ -1393,6 +1880,7 @@ static int qcom_nandc_write_page_raw(str
|
|
int i, ret;
|
|
|
|
clear_read_regs(nandc);
|
|
+ clear_bam_transaction(nandc);
|
|
|
|
data_buf = (u8 *)buf;
|
|
oob_buf = chip->oob_poi;
|
|
@@ -1419,19 +1907,22 @@ static int qcom_nandc_write_page_raw(str
|
|
|
|
config_cw_write_pre(nandc);
|
|
|
|
- write_data_dma(nandc, reg_off, data_buf, data_size1);
|
|
+ write_data_dma(nandc, reg_off, data_buf, data_size1,
|
|
+ DMA_DESC_FLAG_NO_EOT);
|
|
reg_off += data_size1;
|
|
data_buf += data_size1;
|
|
|
|
- write_data_dma(nandc, reg_off, oob_buf, oob_size1);
|
|
+ write_data_dma(nandc, reg_off, oob_buf, oob_size1,
|
|
+ DMA_DESC_FLAG_NO_EOT);
|
|
reg_off += oob_size1;
|
|
oob_buf += oob_size1;
|
|
|
|
- write_data_dma(nandc, reg_off, data_buf, data_size2);
|
|
+ write_data_dma(nandc, reg_off, data_buf, data_size2,
|
|
+ DMA_DESC_FLAG_NO_EOT);
|
|
reg_off += data_size2;
|
|
data_buf += data_size2;
|
|
|
|
- write_data_dma(nandc, reg_off, oob_buf, oob_size2);
|
|
+ write_data_dma(nandc, reg_off, oob_buf, oob_size2, 0);
|
|
oob_buf += oob_size2;
|
|
|
|
config_cw_write_post(nandc);
|
|
@@ -1467,6 +1958,7 @@ static int qcom_nandc_write_oob(struct m
|
|
|
|
host->use_ecc = true;
|
|
|
|
+ clear_bam_transaction(nandc);
|
|
ret = copy_last_cw(host, page);
|
|
if (ret)
|
|
return ret;
|
|
@@ -1486,7 +1978,7 @@ static int qcom_nandc_write_oob(struct m
|
|
|
|
config_cw_write_pre(nandc);
|
|
write_data_dma(nandc, FLASH_BUF_ACC, nandc->data_buffer,
|
|
- data_size + oob_size);
|
|
+ data_size + oob_size, 0);
|
|
config_cw_write_post(nandc);
|
|
|
|
ret = submit_descs(nandc);
|
|
@@ -1524,6 +2016,7 @@ static int qcom_nandc_block_bad(struct m
|
|
*/
|
|
host->use_ecc = false;
|
|
|
|
+ clear_bam_transaction(nandc);
|
|
ret = copy_last_cw(host, page);
|
|
if (ret)
|
|
goto err;
|
|
@@ -1554,6 +2047,7 @@ static int qcom_nandc_block_markbad(stru
|
|
int page, ret, status = 0;
|
|
|
|
clear_read_regs(nandc);
|
|
+ clear_bam_transaction(nandc);
|
|
|
|
/*
|
|
* to mark the BBM as bad, we flash the entire last codeword with 0s.
|
|
@@ -1570,7 +2064,8 @@ static int qcom_nandc_block_markbad(stru
|
|
update_rw_regs(host, 1, false);
|
|
|
|
config_cw_write_pre(nandc);
|
|
- write_data_dma(nandc, FLASH_BUF_ACC, nandc->data_buffer, host->cw_size);
|
|
+ write_data_dma(nandc, FLASH_BUF_ACC, nandc->data_buffer,
|
|
+ host->cw_size, 0);
|
|
config_cw_write_post(nandc);
|
|
|
|
ret = submit_descs(nandc);
|
|
@@ -1930,6 +2425,8 @@ static int qcom_nand_host_setup(struct q
|
|
|
|
host->clrflashstatus = FS_READY_BSY_N;
|
|
host->clrreadstatus = 0xc0;
|
|
+ nandc->regs->erased_cw_detect_cfg_clr = CLR_ERASED_PAGE_DET;
|
|
+ nandc->regs->erased_cw_detect_cfg_set = SET_ERASED_PAGE_DET;
|
|
|
|
dev_dbg(nandc->dev,
|
|
"cfg0 %x cfg1 %x ecc_buf_cfg %x ecc_bch cfg %x cw_size %d cw_data %d strength %d parity_bytes %d steps %d\n",
|
|
@@ -2008,6 +2505,12 @@ static int qcom_nandc_alloc(struct qcom_
|
|
dev_err(nandc->dev, "failed to request cmd channel\n");
|
|
return -ENODEV;
|
|
}
|
|
+
|
|
+ nandc->bam_txn = alloc_bam_transaction(nandc);
|
|
+ if (!nandc->bam_txn) {
|
|
+ dev_err(nandc->dev, "failed to allocate bam transaction\n");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
}
|
|
|
|
INIT_LIST_HEAD(&nandc->desc_list);
|
|
@@ -2043,6 +2546,9 @@ static void qcom_nandc_unalloc(struct qc
|
|
devm_kfree(nandc->dev, nandc->reg_read_buf);
|
|
}
|
|
|
|
+ if (nandc->bam_txn)
|
|
+ devm_kfree(nandc->dev, nandc->bam_txn);
|
|
+
|
|
if (nandc->regs)
|
|
devm_kfree(nandc->dev, nandc->regs);
|
|
|
|
@@ -2053,11 +2559,18 @@ static void qcom_nandc_unalloc(struct qc
|
|
/* one time setup of a few nand controller registers */
|
|
static int qcom_nandc_setup(struct qcom_nand_controller *nandc)
|
|
{
|
|
+ u32 nand_ctrl;
|
|
+
|
|
/* kill onenand */
|
|
nandc_write(nandc, SFLASHC_BURST_CFG, 0);
|
|
|
|
- /* enable ADM DMA */
|
|
- nandc_write(nandc, NAND_FLASH_CHIP_SELECT, DM_EN);
|
|
+ /* enable ADM or BAM DMA */
|
|
+ if (!nandc->dma_bam_enabled) {
|
|
+ nandc_write(nandc, NAND_FLASH_CHIP_SELECT, DM_EN);
|
|
+ } else {
|
|
+ nand_ctrl = nandc_read(nandc, NAND_CTRL);
|
|
+ nandc_write(nandc, NAND_CTRL, nand_ctrl | BAM_MODE_EN);
|
|
+ }
|
|
|
|
/* save the original values of these registers */
|
|
nandc->cmd1 = nandc_read(nandc, NAND_DEV_CMD1);
|
|
--- /dev/null
|
|
+++ b/include/linux/dma/qcom_bam_dma.h
|
|
@@ -0,0 +1,149 @@
|
|
+/*
|
|
+ * Copyright (c) 2017, The Linux Foundation. All rights reserved.
|
|
+ *
|
|
+ * Permission to use, copy, modify, and/or distribute this software for any
|
|
+ * purpose with or without fee is hereby granted, provided that the above
|
|
+ * copyright notice and this permission notice appear in all copies.
|
|
+ *
|
|
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
+ */
|
|
+
|
|
+#ifndef _QCOM_BAM_DMA_H
|
|
+#define _QCOM_BAM_DMA_H
|
|
+
|
|
+#include <linux/dma-mapping.h>
|
|
+
|
|
+#define DESC_FLAG_INT BIT(15)
|
|
+#define DESC_FLAG_EOT BIT(14)
|
|
+#define DESC_FLAG_EOB BIT(13)
|
|
+#define DESC_FLAG_NWD BIT(12)
|
|
+#define DESC_FLAG_CMD BIT(11)
|
|
+
|
|
+/*
|
|
+ * QCOM BAM DMA SGL struct
|
|
+ *
|
|
+ * @sgl: DMA SGL
|
|
+ * @dma_flags: BAM DMA flags
|
|
+ */
|
|
+struct qcom_bam_sgl {
|
|
+ struct scatterlist sgl;
|
|
+ unsigned int dma_flags;
|
|
+};
|
|
+
|
|
+/*
|
|
+ * This data type corresponds to the native Command Element
|
|
+ * supported by BAM DMA Engine.
|
|
+ *
|
|
+ * @addr - register address.
|
|
+ * @command - command type.
|
|
+ * @data - for write command: content to be written into peripheral register.
|
|
+ * for read command: dest addr to write peripheral register value to.
|
|
+ * @mask - register mask.
|
|
+ * @reserved - for future usage.
|
|
+ *
|
|
+ */
|
|
+struct bam_cmd_element {
|
|
+ __le32 addr:24;
|
|
+ __le32 command:8;
|
|
+ __le32 data;
|
|
+ __le32 mask;
|
|
+ __le32 reserved;
|
|
+};
|
|
+
|
|
+/*
|
|
+ * This enum indicates the command type in a command element
|
|
+ */
|
|
+enum bam_command_type {
|
|
+ BAM_WRITE_COMMAND = 0,
|
|
+ BAM_READ_COMMAND,
|
|
+};
|
|
+
|
|
+/*
|
|
+ * qcom_bam_sg_init_table - Init QCOM BAM SGL
|
|
+ * @bam_sgl: bam sgl
|
|
+ * @nents: number of entries in bam sgl
|
|
+ *
|
|
+ * This function performs the initialization for each SGL in BAM SGL
|
|
+ * with generic SGL API.
|
|
+ */
|
|
+static inline void qcom_bam_sg_init_table(struct qcom_bam_sgl *bam_sgl,
|
|
+ unsigned int nents)
|
|
+{
|
|
+ int i;
|
|
+
|
|
+ for (i = 0; i < nents; i++)
|
|
+ sg_init_table(&bam_sgl[i].sgl, 1);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * qcom_bam_unmap_sg - Unmap QCOM BAM SGL
|
|
+ * @dev: device for which unmapping needs to be done
|
|
+ * @bam_sgl: bam sgl
|
|
+ * @nents: number of entries in bam sgl
|
|
+ * @dir: dma transfer direction
|
|
+ *
|
|
+ * This function performs the DMA unmapping for each SGL in BAM SGL
|
|
+ * with generic SGL API.
|
|
+ */
|
|
+static inline void qcom_bam_unmap_sg(struct device *dev,
|
|
+ struct qcom_bam_sgl *bam_sgl, int nents, enum dma_data_direction dir)
|
|
+{
|
|
+ int i;
|
|
+
|
|
+ for (i = 0; i < nents; i++)
|
|
+ dma_unmap_sg(dev, &bam_sgl[i].sgl, 1, dir);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * qcom_bam_map_sg - Map QCOM BAM SGL
|
|
+ * @dev: device for which mapping needs to be done
|
|
+ * @bam_sgl: bam sgl
|
|
+ * @nents: number of entries in bam sgl
|
|
+ * @dir: dma transfer direction
|
|
+ *
|
|
+ * This function performs the DMA mapping for each SGL in BAM SGL
|
|
+ * with generic SGL API.
|
|
+ *
|
|
+ * returns 0 on error and > 0 on success
|
|
+ */
|
|
+static inline int qcom_bam_map_sg(struct device *dev,
|
|
+ struct qcom_bam_sgl *bam_sgl, int nents, enum dma_data_direction dir)
|
|
+{
|
|
+ int i, ret = 0;
|
|
+
|
|
+ for (i = 0; i < nents; i++) {
|
|
+ ret = dma_map_sg(dev, &bam_sgl[i].sgl, 1, dir);
|
|
+ if (!ret)
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ /* unmap the mapped sgl from previous loop in case of error */
|
|
+ if (!ret)
|
|
+ qcom_bam_unmap_sg(dev, bam_sgl, i, dir);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * qcom_prep_bam_ce - Wrapper function to prepare a single BAM command element
|
|
+ * with the data that is passed to this function.
|
|
+ * @bam_ce: bam command element
|
|
+ * @addr: target address
|
|
+ * @command: command in bam_command_type
|
|
+ * @data: actual data for write and dest addr for read
|
|
+ */
|
|
+static inline void qcom_prep_bam_ce(struct bam_cmd_element *bam_ce,
|
|
+ uint32_t addr, uint32_t command, uint32_t data)
|
|
+{
|
|
+ bam_ce->addr = cpu_to_le32(addr);
|
|
+ bam_ce->command = cpu_to_le32(command);
|
|
+ bam_ce->data = cpu_to_le32(data);
|
|
+ bam_ce->mask = 0xFFFFFFFF;
|
|
+}
|
|
+#endif
|