|
@@ -30,6 +30,8 @@
|
|
#include <linux/clk.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/err.h>
|
|
#include <linux/err.h>
|
|
#include <linux/io.h>
|
|
#include <linux/io.h>
|
|
|
|
+#include <linux/irq.h>
|
|
|
|
+#include <linux/completion.h>
|
|
|
|
|
|
#include <asm/mach/flash.h>
|
|
#include <asm/mach/flash.h>
|
|
#include <mach/mxc_nand.h>
|
|
#include <mach/mxc_nand.h>
|
|
@@ -151,7 +153,7 @@ struct mxc_nand_host {
|
|
int irq;
|
|
int irq;
|
|
int eccsize;
|
|
int eccsize;
|
|
|
|
|
|
- wait_queue_head_t irq_waitq;
|
|
|
|
|
|
+ struct completion op_completion;
|
|
|
|
|
|
uint8_t *data_buf;
|
|
uint8_t *data_buf;
|
|
unsigned int buf_start;
|
|
unsigned int buf_start;
|
|
@@ -164,6 +166,7 @@ struct mxc_nand_host {
|
|
void (*send_read_id)(struct mxc_nand_host *);
|
|
void (*send_read_id)(struct mxc_nand_host *);
|
|
uint16_t (*get_dev_status)(struct mxc_nand_host *);
|
|
uint16_t (*get_dev_status)(struct mxc_nand_host *);
|
|
int (*check_int)(struct mxc_nand_host *);
|
|
int (*check_int)(struct mxc_nand_host *);
|
|
|
|
+ void (*irq_control)(struct mxc_nand_host *, int);
|
|
};
|
|
};
|
|
|
|
|
|
/* OOB placement block for use with hardware ecc generation */
|
|
/* OOB placement block for use with hardware ecc generation */
|
|
@@ -216,9 +219,12 @@ static irqreturn_t mxc_nfc_irq(int irq, void *dev_id)
|
|
{
|
|
{
|
|
struct mxc_nand_host *host = dev_id;
|
|
struct mxc_nand_host *host = dev_id;
|
|
|
|
|
|
- disable_irq_nosync(irq);
|
|
|
|
|
|
+ if (!host->check_int(host))
|
|
|
|
+ return IRQ_NONE;
|
|
|
|
|
|
- wake_up(&host->irq_waitq);
|
|
|
|
|
|
+ host->irq_control(host, 0);
|
|
|
|
+
|
|
|
|
+ complete(&host->op_completion);
|
|
|
|
|
|
return IRQ_HANDLED;
|
|
return IRQ_HANDLED;
|
|
}
|
|
}
|
|
@@ -245,11 +251,54 @@ static int check_int_v1_v2(struct mxc_nand_host *host)
|
|
if (!(tmp & NFC_V1_V2_CONFIG2_INT))
|
|
if (!(tmp & NFC_V1_V2_CONFIG2_INT))
|
|
return 0;
|
|
return 0;
|
|
|
|
|
|
- writew(tmp & ~NFC_V1_V2_CONFIG2_INT, NFC_V1_V2_CONFIG2);
|
|
|
|
|
|
+ if (!cpu_is_mx21())
|
|
|
|
+ writew(tmp & ~NFC_V1_V2_CONFIG2_INT, NFC_V1_V2_CONFIG2);
|
|
|
|
|
|
return 1;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+/*
|
|
|
|
+ * It has been observed that the i.MX21 cannot read the CONFIG2:INT bit
|
|
|
|
+ * if interrupts are masked (CONFIG1:INT_MSK is set). To handle this, the
|
|
|
|
+ * driver can enable/disable the irq line rather than simply masking the
|
|
|
|
+ * interrupts.
|
|
|
|
+ */
|
|
|
|
+static void irq_control_mx21(struct mxc_nand_host *host, int activate)
|
|
|
|
+{
|
|
|
|
+ if (activate)
|
|
|
|
+ enable_irq(host->irq);
|
|
|
|
+ else
|
|
|
|
+ disable_irq_nosync(host->irq);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void irq_control_v1_v2(struct mxc_nand_host *host, int activate)
|
|
|
|
+{
|
|
|
|
+ uint16_t tmp;
|
|
|
|
+
|
|
|
|
+ tmp = readw(NFC_V1_V2_CONFIG1);
|
|
|
|
+
|
|
|
|
+ if (activate)
|
|
|
|
+ tmp &= ~NFC_V1_V2_CONFIG1_INT_MSK;
|
|
|
|
+ else
|
|
|
|
+ tmp |= NFC_V1_V2_CONFIG1_INT_MSK;
|
|
|
|
+
|
|
|
|
+ writew(tmp, NFC_V1_V2_CONFIG1);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void irq_control_v3(struct mxc_nand_host *host, int activate)
|
|
|
|
+{
|
|
|
|
+ uint32_t tmp;
|
|
|
|
+
|
|
|
|
+ tmp = readl(NFC_V3_CONFIG2);
|
|
|
|
+
|
|
|
|
+ if (activate)
|
|
|
|
+ tmp &= ~NFC_V3_CONFIG2_INT_MSK;
|
|
|
|
+ else
|
|
|
|
+ tmp |= NFC_V3_CONFIG2_INT_MSK;
|
|
|
|
+
|
|
|
|
+ writel(tmp, NFC_V3_CONFIG2);
|
|
|
|
+}
|
|
|
|
+
|
|
/* This function polls the NANDFC to wait for the basic operation to
|
|
/* This function polls the NANDFC to wait for the basic operation to
|
|
* complete by checking the INT bit of config2 register.
|
|
* complete by checking the INT bit of config2 register.
|
|
*/
|
|
*/
|
|
@@ -259,10 +308,9 @@ static void wait_op_done(struct mxc_nand_host *host, int useirq)
|
|
|
|
|
|
if (useirq) {
|
|
if (useirq) {
|
|
if (!host->check_int(host)) {
|
|
if (!host->check_int(host)) {
|
|
-
|
|
|
|
- enable_irq(host->irq);
|
|
|
|
-
|
|
|
|
- wait_event(host->irq_waitq, host->check_int(host));
|
|
|
|
|
|
+ INIT_COMPLETION(host->op_completion);
|
|
|
|
+ host->irq_control(host, 1);
|
|
|
|
+ wait_for_completion(&host->op_completion);
|
|
}
|
|
}
|
|
} else {
|
|
} else {
|
|
while (max_retries-- > 0) {
|
|
while (max_retries-- > 0) {
|
|
@@ -799,6 +847,7 @@ static void preset_v3(struct mtd_info *mtd)
|
|
NFC_V3_CONFIG2_2CMD_PHASES |
|
|
NFC_V3_CONFIG2_2CMD_PHASES |
|
|
NFC_V3_CONFIG2_SPAS(mtd->oobsize >> 1) |
|
|
NFC_V3_CONFIG2_SPAS(mtd->oobsize >> 1) |
|
|
NFC_V3_CONFIG2_ST_CMD(0x70) |
|
|
NFC_V3_CONFIG2_ST_CMD(0x70) |
|
|
|
|
+ NFC_V3_CONFIG2_INT_MSK |
|
|
NFC_V3_CONFIG2_NUM_ADDR_PHASE0;
|
|
NFC_V3_CONFIG2_NUM_ADDR_PHASE0;
|
|
|
|
|
|
if (chip->ecc.mode == NAND_ECC_HW)
|
|
if (chip->ecc.mode == NAND_ECC_HW)
|
|
@@ -1024,6 +1073,10 @@ static int __init mxcnd_probe(struct platform_device *pdev)
|
|
host->send_read_id = send_read_id_v1_v2;
|
|
host->send_read_id = send_read_id_v1_v2;
|
|
host->get_dev_status = get_dev_status_v1_v2;
|
|
host->get_dev_status = get_dev_status_v1_v2;
|
|
host->check_int = check_int_v1_v2;
|
|
host->check_int = check_int_v1_v2;
|
|
|
|
+ if (cpu_is_mx21())
|
|
|
|
+ host->irq_control = irq_control_mx21;
|
|
|
|
+ else
|
|
|
|
+ host->irq_control = irq_control_v1_v2;
|
|
}
|
|
}
|
|
|
|
|
|
if (nfc_is_v21()) {
|
|
if (nfc_is_v21()) {
|
|
@@ -1062,6 +1115,7 @@ static int __init mxcnd_probe(struct platform_device *pdev)
|
|
host->send_read_id = send_read_id_v3;
|
|
host->send_read_id = send_read_id_v3;
|
|
host->check_int = check_int_v3;
|
|
host->check_int = check_int_v3;
|
|
host->get_dev_status = get_dev_status_v3;
|
|
host->get_dev_status = get_dev_status_v3;
|
|
|
|
+ host->irq_control = irq_control_v3;
|
|
oob_smallpage = &nandv2_hw_eccoob_smallpage;
|
|
oob_smallpage = &nandv2_hw_eccoob_smallpage;
|
|
oob_largepage = &nandv2_hw_eccoob_largepage;
|
|
oob_largepage = &nandv2_hw_eccoob_largepage;
|
|
} else
|
|
} else
|
|
@@ -1093,14 +1147,34 @@ static int __init mxcnd_probe(struct platform_device *pdev)
|
|
this->options |= NAND_USE_FLASH_BBT;
|
|
this->options |= NAND_USE_FLASH_BBT;
|
|
}
|
|
}
|
|
|
|
|
|
- init_waitqueue_head(&host->irq_waitq);
|
|
|
|
|
|
+ init_completion(&host->op_completion);
|
|
|
|
|
|
host->irq = platform_get_irq(pdev, 0);
|
|
host->irq = platform_get_irq(pdev, 0);
|
|
|
|
|
|
|
|
+ /*
|
|
|
|
+ * mask the interrupt. For i.MX21 explicitely call
|
|
|
|
+ * irq_control_v1_v2 to use the mask bit. We can't call
|
|
|
|
+ * disable_irq_nosync() for an interrupt we do not own yet.
|
|
|
|
+ */
|
|
|
|
+ if (cpu_is_mx21())
|
|
|
|
+ irq_control_v1_v2(host, 0);
|
|
|
|
+ else
|
|
|
|
+ host->irq_control(host, 0);
|
|
|
|
+
|
|
err = request_irq(host->irq, mxc_nfc_irq, IRQF_DISABLED, DRIVER_NAME, host);
|
|
err = request_irq(host->irq, mxc_nfc_irq, IRQF_DISABLED, DRIVER_NAME, host);
|
|
if (err)
|
|
if (err)
|
|
goto eirq;
|
|
goto eirq;
|
|
|
|
|
|
|
|
+ host->irq_control(host, 0);
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * Now that the interrupt is disabled make sure the interrupt
|
|
|
|
+ * mask bit is cleared on i.MX21. Otherwise we can't read
|
|
|
|
+ * the interrupt status bit on this machine.
|
|
|
|
+ */
|
|
|
|
+ if (cpu_is_mx21())
|
|
|
|
+ irq_control_v1_v2(host, 1);
|
|
|
|
+
|
|
/* first scan to find the device and get the page size */
|
|
/* first scan to find the device and get the page size */
|
|
if (nand_scan_ident(mtd, 1, NULL)) {
|
|
if (nand_scan_ident(mtd, 1, NULL)) {
|
|
err = -ENXIO;
|
|
err = -ENXIO;
|