From 62816e3bbb383dea4f437e83f89ffc454b31579c Mon Sep 17 00:00:00 2001 From: Chen Liangjun Date: Wed, 12 Sep 2012 18:34:28 +0800 Subject: ENGR00223816 HDMI AUDIO: fix kernel panic cause by accessing unavailable memory HDMI audio driver is responsible for add IEC header into audio sample. In HDMI audio driver, a variable named rtd->appl_bytes is maintained to stand for how many audio sample have already processed. appl_bytes stands for how many audio sample the user space have already feed into kernel driver. So we use the connt = appl_bytes - rtd->appl_bytes to decide how many data need to be processed. And the processed data would be write into an preallocated buffer called hw_buf in driver. When doing seek operation, the appl_bytes changes in an wide range. So it is possible that the count value is far larger than the size of hw_buf and the memory access un-existed address error would happens. In this patch, Add check operation for count to avoid kernel panic. Kernel panic log: seeking: 0:00:18.000000000/0:03:0Unable to handle kernel paging request at virtual address ffdf0000 pgd = 80004000 [ffdf0000] *pgd=71e35811, *pte=00000000, *ppte=00000000 Internal error: Oops: 7 [#1] PREEMPT SMP Modules linked in: vivante drm galcore CPU: 0 Not tainted (3.0.35-2014-g7a9337b #1) PC is at hdmi_dma_mmap_copy+0x134/0x190 LR is at hdmi_dma_mmap_copy+0x5c/0x190 pc : [<803e1e4c>] lr : [<803e1d74>] psr: 800f0193 sp : 80a61e98 ip : ffdf0000 fp : ffdeffc0 r10: 00000055 r9 : ffdeff80 r8 : 0029b450 r7 : 00000060 r6 : ffdf0200 r5 : 00000240 r4 : 00000120 r3 : 00000000 r2 : ffdf0000 r1 : 00000000 r0 : 00000090 Flags: Nzcv IRQs off FIQs on Mode SVC_32 ISA ARM Segment kernel Control: 10c53c7d Table: 6d28804a DAC: 00000015 Process swapper (pid: 0, stack limit = 0x80a602f0) Stack: (0x80a61e98 to 0x80a62000) 1e80: 00000002 000037f1 1ea0: 00006000 e1d3a7e4 a00f0193 e1d3a780 e1dcab00 00a83100 00a83100 00006000 1ec0: 00006000 803e24f4 413187b9 00000000 0073001a e1dd1e00 00000001 00000001 1ee0: 00000080 00000093 80ac7070 80a66a80 00000001 800a5ca8 8c80e568 efb3e7b9 1f00: 00000055 80a66a80 80a66acc e1ea9bc0 00000093 00000000 80a60000 00000000 1f20: 00000000 800a5e14 80a66a80 80a66acc 0000107f 800a8198 80a71cc0 80038c00 1f40: 80a60000 800a5610 000001f0 80040830 ffffffff f2a00100 00000093 00000002 1f60: 00000001 8003f9cc 80ac5f60 800f0093 00000001 00000000 80a60000 80abeb64 1f80: 804e1a54 80a74e7c 1000406a 412fc09a 00000000 00000000 00000000 80a61fb0 1fa0: 8004d52c 80040ac4 400f0013 ffffffff 80040aa0 80040cbc 00000001 80a71b3c 1fc0: 80abeac0 8002e3c4 8c80b140 80008868 800082f8 00000000 00000000 8002e3c4 1fe0: 00000000 10c53c7d 80a71a6c 8002e3c0 80a74e74 10008040 00000000 00000000 [<803e1e4c>] (hdmi_dma_mmap_copy+0x134/0x190) from [<803e24f4>] (hdmi_dma_isr+0x17c/0x1a0) [<803e24f4>] (hdmi_dma_isr+0x17c/0x1a0) from [<800a5ca8>] (handle_irq_event_percpu+0x50/0x180) [<800a5ca8>] (handle_irq_event_percpu+0x50/0x180) from [<800a5e14>] (handle_irq_event+0x3c/0x5c) [<800a5e14>] (handle_irq_event+0x3c/0x5c) from [<800a8198>] (handle_fasteoi_irq+0xbc/0x154) [<800a8198>] (handle_fasteoi_irq+0xbc/0x154) from [<800a5610>] (generic_handle_irq+0x28/0x3c) [<800a5610>] (generic_handle_irq+0x28/0x3c) from [<80040830>] (handle_IRQ+0x4c/0xac) [<80040830>] (handle_IRQ+0x4c/0xac) from [<8003f9cc>] (__irq_svc+0x4c/0xe8) [<8003f9cc>] (__irq_svc+0x4c/0xe8) from [<80040ac4>] (default_idle+0x24/0x28) [<80040ac4>] (default_idle+0x24/0x28) from [<80040cbc>] (cpu_idle+0xbc/0xfc) [<80040cbc>] (cpu_idle+0xbc/0xfc) from [<80008868>] (start_kernel+0x248/0x288) [<80008868>] (start_kernel+0x248/0x288) from [<10008040>] (0x10008040) Code: c1a0c009 c08b6005 c1a0200b da00000a (e0d230b2) Signed-off-by: Chen Liangjun --- sound/soc/imx/imx-hdmi-dma.c | 63 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 49 insertions(+), 14 deletions(-) diff --git a/sound/soc/imx/imx-hdmi-dma.c b/sound/soc/imx/imx-hdmi-dma.c index 45811429985f..ab0207a428d0 100644 --- a/sound/soc/imx/imx-hdmi-dma.c +++ b/sound/soc/imx/imx-hdmi-dma.c @@ -597,15 +597,27 @@ static void hdmi_sdma_isr(void *data) if (runtime->access == SNDRV_PCM_ACCESS_MMAP_INTERLEAVED) { appl_bytes = frames_to_bytes(runtime, runtime->control->appl_ptr); - if (rtd->appl_bytes > appl_bytes) - rtd->appl_bytes = 0; + + if (rtd->appl_bytes > appl_bytes) { + if (appl_bytes > rtd->buffer_bytes) + rtd->appl_bytes = + appl_bytes - rtd->buffer_bytes; + else + rtd->appl_bytes = 0; + } else { + if ((appl_bytes - rtd->appl_bytes) > + rtd->buffer_bytes) + rtd->appl_bytes = + appl_bytes - rtd->buffer_bytes; + + } + offset = rtd->appl_bytes % rtd->buffer_bytes; space_to_end = rtd->buffer_bytes - offset; count = appl_bytes - rtd->appl_bytes; - if (count > rtd->buffer_bytes) { - pr_info("HDMI is slow,ring buffer size[%ld], count[%ld]!\n", - rtd->buffer_bytes, count); - } + if (count > rtd->buffer_bytes) + count = rtd->buffer_bytes; + rtd->appl_bytes = appl_bytes; if (count <= space_to_end) { @@ -651,15 +663,25 @@ static irqreturn_t hdmi_dma_isr(int irq, void *dev_id) if (runtime->access == SNDRV_PCM_ACCESS_MMAP_INTERLEAVED) { appl_bytes = frames_to_bytes(runtime, runtime->control->appl_ptr); - if (rtd->appl_bytes > appl_bytes) - rtd->appl_bytes = 0; + if (rtd->appl_bytes > appl_bytes) { + if (appl_bytes > rtd->buffer_bytes) + rtd->appl_bytes = + appl_bytes - rtd->buffer_bytes; + else + rtd->appl_bytes = 0; + } else { + if ((appl_bytes - rtd->appl_bytes) > + rtd->buffer_bytes) + rtd->appl_bytes = + appl_bytes - rtd->buffer_bytes; + + } + offset = rtd->appl_bytes % rtd->buffer_bytes; space_to_end = rtd->buffer_bytes - offset; count = appl_bytes - rtd->appl_bytes; - if (count > rtd->buffer_bytes) { - pr_info("HDMI is slow,ring buffer size[%ld],count[%ld]!\n", - rtd->buffer_bytes, count); - } + if (count > rtd->buffer_bytes) + count = rtd->buffer_bytes; rtd->appl_bytes = appl_bytes; if (count <= space_to_end) { @@ -1101,8 +1123,20 @@ static int hdmi_dma_trigger(struct snd_pcm_substream *substream, int cmd) * keep the old value but the control-> * appl_ptr is clear. Reset it if this * misalignment happens*/ - if (rtd->appl_bytes > appl_bytes) - rtd->appl_bytes = 0; + if (rtd->appl_bytes > appl_bytes) { + if (appl_bytes > rtd->buffer_bytes) + rtd->appl_bytes = + appl_bytes - rtd->buffer_bytes; + else + rtd->appl_bytes = 0; + } else { + if ((appl_bytes - rtd->appl_bytes) > + rtd->buffer_bytes) + rtd->appl_bytes = + appl_bytes - rtd->buffer_bytes; + + } + offset = rtd->appl_bytes % rtd->buffer_bytes; space_to_end = rtd->buffer_bytes - offset; count = appl_bytes - rtd->appl_bytes; @@ -1123,6 +1157,7 @@ static int hdmi_dma_trigger(struct snd_pcm_substream *substream, int cmd) hdmi_dma_mmap_copy(substream, 0, count - space_to_end); } + } dumpregs(); -- cgit v1.2.3 From 3d41e2e943bc74b816f7002b501092fc64384824 Mon Sep 17 00:00:00 2001 From: Huang Shijie Date: Fri, 7 Sep 2012 10:51:01 +0800 Subject: ENGR00223349-1 gpmi: add a new field for HW_GPMI_TIMING1 The gpmi_nfc_compute_hardware_timing{} should contains all the fields setting for gpmi timing registers. It already contains the fields for HW_GPMI_TIMING0 and HW_GPMI_CTRL1. So it is better to add a new field setting for HW_GPMI_TIMING1 in this data structure. This makes the code more clear in logic. This patch also changs some comments to make the code more readable. Signed-off-by: Huang Shijie --- drivers/mtd/nand/gpmi-nand/gpmi-lib.c | 34 +++++++++++++--------------------- drivers/mtd/nand/gpmi-nand/gpmi-nand.h | 9 +++++++++ 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/drivers/mtd/nand/gpmi-nand/gpmi-lib.c b/drivers/mtd/nand/gpmi-nand/gpmi-lib.c index 75c2e618b28a..8e28df497635 100644 --- a/drivers/mtd/nand/gpmi-nand/gpmi-lib.c +++ b/drivers/mtd/nand/gpmi-nand/gpmi-lib.c @@ -989,6 +989,7 @@ return_results: hw->address_setup_in_cycles = address_setup_in_cycles; hw->use_half_periods = dll_use_half_periods; hw->sample_delay_factor = sample_delay_factor; + hw->device_busy_timeout = 0x500; /* default busy timeout value. */ /* Return success. */ return 0; @@ -1020,26 +1021,26 @@ void gpmi_begin(struct gpmi_nand_data *this) } } - /* set ready/busy timeout */ - writel(0x500 << BP_GPMI_TIMING1_BUSY_TIMEOUT, - gpmi_regs + HW_GPMI_TIMING1); - /* Get the timing information we need. */ nfc->clock_frequency_in_hz = clk_get_rate(r->clock); clock_period_in_ns = 1000000000 / nfc->clock_frequency_in_hz; gpmi_nfc_compute_hardware_timing(this, &hw); - /* Set up all the simple timing parameters. */ + /* [1] Set HW_GPMI_TIMING0 */ reg = BF_GPMI_TIMING0_ADDRESS_SETUP(hw.address_setup_in_cycles) | BF_GPMI_TIMING0_DATA_HOLD(hw.data_hold_in_cycles) | BF_GPMI_TIMING0_DATA_SETUP(hw.data_setup_in_cycles) ; writel(reg, gpmi_regs + HW_GPMI_TIMING0); - /* - * DLL_ENABLE must be set to 0 when setting RDN_DELAY or HALF_PERIOD. - */ + /* [2] Set HW_GPMI_TIMING1 */ + writel(BF_GPMI_TIMING1_DEVICE_BUSY_TIMEOUT(hw.device_busy_timeout), + gpmi_regs + HW_GPMI_TIMING1); + + /* [3] The following code is to set the HW_GPMI_CTRL1. */ + + /* DLL_ENABLE must be set to 0 when setting RDN_DELAY or HALF_PERIOD. */ writel(BM_GPMI_CTRL1_DLL_ENABLE, gpmi_regs + HW_GPMI_CTRL1_CLR); /* Clear out the DLL control fields. */ @@ -1050,30 +1051,21 @@ void gpmi_begin(struct gpmi_nand_data *this) if (!hw.sample_delay_factor) return; - /* Configure the HALF_PERIOD flag. */ if (hw.use_half_periods) writel(BM_GPMI_CTRL1_HALF_PERIOD, - gpmi_regs + HW_GPMI_CTRL1_SET); - - /* Set the delay factor. */ + gpmi_regs + HW_GPMI_CTRL1_SET); writel(BF_GPMI_CTRL1_RDN_DELAY(hw.sample_delay_factor), - gpmi_regs + HW_GPMI_CTRL1_SET); - - /* Enable the DLL. */ + gpmi_regs + HW_GPMI_CTRL1_SET); writel(BM_GPMI_CTRL1_DLL_ENABLE, gpmi_regs + HW_GPMI_CTRL1_SET); /* * After we enable the GPMI DLL, we have to wait 64 clock cycles before - * we can use the GPMI. - * - * Calculate the amount of time we need to wait, in microseconds. + * we can use the GPMI. Calculate the amount of time we need to wait, + * in microseconds. */ dll_wait_time_in_us = (clock_period_in_ns * 64) / 1000; - if (!dll_wait_time_in_us) dll_wait_time_in_us = 1; - - /* Wait for the DLL to settle. */ udelay(dll_wait_time_in_us); err_out: diff --git a/drivers/mtd/nand/gpmi-nand/gpmi-nand.h b/drivers/mtd/nand/gpmi-nand/gpmi-nand.h index 229b2c5e1b12..cf50a6055511 100644 --- a/drivers/mtd/nand/gpmi-nand/gpmi-nand.h +++ b/drivers/mtd/nand/gpmi-nand/gpmi-nand.h @@ -188,14 +188,23 @@ struct gpmi_nand_data { * @data_setup_in_cycles: The data setup time, in cycles. * @data_hold_in_cycles: The data hold time, in cycles. * @address_setup_in_cycles: The address setup time, in cycles. + * @device_busy_timeout: The timeout waiting for NAND Ready/Busy, + * this value is the number of cycles multiplied + * by 4096. * @use_half_periods: Indicates the clock is running slowly, so the * NFC DLL should use half-periods. * @sample_delay_factor: The sample delay factor. */ struct gpmi_nfc_hardware_timing { + /* for GPMI_HW_GPMI_TIMING0 */ uint8_t data_setup_in_cycles; uint8_t data_hold_in_cycles; uint8_t address_setup_in_cycles; + + /* for GPMI_HW_GPMI_TIMING1 */ + uint16_t device_busy_timeout; + + /* for GPMI_HW_GPMI_CTRL1 */ bool use_half_periods; uint8_t sample_delay_factor; }; -- cgit v1.2.3 From 3220313f2cfc41ce3d0796aa755c4544a81a16fd Mon Sep 17 00:00:00 2001 From: Huang Shijie Date: Fri, 7 Sep 2012 13:00:58 +0800 Subject: ENGR00223349-2 gpmi: do not get the clock frequency in gpmi_begin() The current code will gets the clock frequency which is used by gpmi_nfc_compute_hardware_timing(). It makes the code a little mess. So move the `get clock frequency` code to the gpmi_nfc_compute_hardware_timing() itself. This makes the code tidy and clean. Signed-off-by: Huang Shijie --- drivers/mtd/nand/gpmi-nand/gpmi-lib.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/mtd/nand/gpmi-nand/gpmi-lib.c b/drivers/mtd/nand/gpmi-nand/gpmi-lib.c index 8e28df497635..a65b6791415f 100644 --- a/drivers/mtd/nand/gpmi-nand/gpmi-lib.c +++ b/drivers/mtd/nand/gpmi-nand/gpmi-lib.c @@ -554,6 +554,7 @@ static int gpmi_nfc_compute_hardware_timing(struct gpmi_nand_data *this, { struct gpmi_nand_platform_data *pdata = this->pdata; struct timing_threshod *nfc = &timing_default_threshold; + struct resources *r = &this->resources; struct nand_chip *nand = &this->nand; struct nand_timing target = this->timing; bool improved_timing_is_available; @@ -593,6 +594,7 @@ static int gpmi_nfc_compute_hardware_timing(struct gpmi_nand_data *this, (target.tRHOH_in_ns >= 0) ; /* Inspect the clock. */ + nfc->clock_frequency_in_hz = clk_get_rate(r->clock); clock_frequency_in_hz = nfc->clock_frequency_in_hz; clock_period_in_ns = 1000000000 / clock_frequency_in_hz; @@ -999,7 +1001,6 @@ return_results: void gpmi_begin(struct gpmi_nand_data *this) { struct resources *r = &this->resources; - struct timing_threshod *nfc = &timing_default_threshold; unsigned char *gpmi_regs = r->gpmi_regs; unsigned int clock_period_in_ns; uint32_t reg; @@ -1021,10 +1022,7 @@ void gpmi_begin(struct gpmi_nand_data *this) } } - /* Get the timing information we need. */ - nfc->clock_frequency_in_hz = clk_get_rate(r->clock); - clock_period_in_ns = 1000000000 / nfc->clock_frequency_in_hz; - + /* calculate the timings. */ gpmi_nfc_compute_hardware_timing(this, &hw); /* [1] Set HW_GPMI_TIMING0 */ @@ -1063,6 +1061,8 @@ void gpmi_begin(struct gpmi_nand_data *this) * we can use the GPMI. Calculate the amount of time we need to wait, * in microseconds. */ + clock_period_in_ns = 1000000000 / clk_get_rate(r->clock); + dll_wait_time_in_us = (clock_period_in_ns * 64) / 1000; if (!dll_wait_time_in_us) dll_wait_time_in_us = 1; -- cgit v1.2.3 From e99dbb43193c818271b456741e5e3a65c93cd9a3 Mon Sep 17 00:00:00 2001 From: Huang Shijie Date: Fri, 7 Sep 2012 14:37:47 +0800 Subject: ENGR00223349-3 gpmi: add a new field for HW_GPMI_CTRL1 add the WRN_DLY_SEL field for HW_GPMI_CTRL1. This field is used as delay for gpmi write strobe. Signed-off-by: Huang Shijie --- drivers/mtd/nand/gpmi-nand/gpmi-lib.c | 4 ++++ drivers/mtd/nand/gpmi-nand/gpmi-nand.h | 2 ++ 2 files changed, 6 insertions(+) diff --git a/drivers/mtd/nand/gpmi-nand/gpmi-lib.c b/drivers/mtd/nand/gpmi-nand/gpmi-lib.c index a65b6791415f..c13ae42a137d 100644 --- a/drivers/mtd/nand/gpmi-nand/gpmi-lib.c +++ b/drivers/mtd/nand/gpmi-nand/gpmi-lib.c @@ -992,6 +992,7 @@ return_results: hw->use_half_periods = dll_use_half_periods; hw->sample_delay_factor = sample_delay_factor; hw->device_busy_timeout = 0x500; /* default busy timeout value. */ + hw->wrn_dly_sel = 0; /* Return success. */ return 0; @@ -1037,6 +1038,9 @@ void gpmi_begin(struct gpmi_nand_data *this) gpmi_regs + HW_GPMI_TIMING1); /* [3] The following code is to set the HW_GPMI_CTRL1. */ + writel(BM_GPMI_CTRL1_WRN_DLY_SEL, gpmi_regs + HW_GPMI_CTRL1_CLR); + writel(BF_GPMI_CTRL1_WRN_DLY_SEL(hw.wrn_dly_sel), + gpmi_regs + HW_GPMI_CTRL1_SET); /* DLL_ENABLE must be set to 0 when setting RDN_DELAY or HALF_PERIOD. */ writel(BM_GPMI_CTRL1_DLL_ENABLE, gpmi_regs + HW_GPMI_CTRL1_CLR); diff --git a/drivers/mtd/nand/gpmi-nand/gpmi-nand.h b/drivers/mtd/nand/gpmi-nand/gpmi-nand.h index cf50a6055511..19b049f64d60 100644 --- a/drivers/mtd/nand/gpmi-nand/gpmi-nand.h +++ b/drivers/mtd/nand/gpmi-nand/gpmi-nand.h @@ -194,6 +194,7 @@ struct gpmi_nand_data { * @use_half_periods: Indicates the clock is running slowly, so the * NFC DLL should use half-periods. * @sample_delay_factor: The sample delay factor. + * @wrn_dly_sel: The delay on the GPMI write strobe. */ struct gpmi_nfc_hardware_timing { /* for GPMI_HW_GPMI_TIMING0 */ @@ -207,6 +208,7 @@ struct gpmi_nfc_hardware_timing { /* for GPMI_HW_GPMI_CTRL1 */ bool use_half_periods; uint8_t sample_delay_factor; + uint8_t wrn_dly_sel; }; /** -- cgit v1.2.3 From 9bba02d47489f2455bae09815b779bdc494efabb Mon Sep 17 00:00:00 2001 From: Huang Shijie Date: Tue, 28 Aug 2012 13:53:49 +0800 Subject: ENGR00223349-4 gpmi: enable the EDO support for mx6q Enable the EDO mode for mx6q. The following is the test result with the same nand chip (Micron MT29F32G08QAA) in mode 4: The test result without enable the EDO mode: ================================================= mtd_speedtest: MTD device: 2 mtd_speedtest: MTD device size 209715200, eraseblock size 524288, page size 4096, count of eraseblocks 400, pages per eraseblock 128, OOB size 218 mtd_speedtest: scanned 400 eraseblocks, 6 are bad mtd_speedtest: testing eraseblock write speed mtd_speedtest: eraseblock write speed is 1945 KiB/s mtd_speedtest: testing eraseblock read speed mtd_speedtest: eraseblock read speed is 3384 KiB/s mtd_speedtest: testing page write speed mtd_speedtest: page write speed is 1841 KiB/s mtd_speedtest: testing page read speed mtd_speedtest: page read speed is 3136 KiB/s mtd_speedtest: testing 2 page write speed mtd_speedtest: 2 page write speed is 1853 KiB/s mtd_speedtest: testing 2 page read speed mtd_speedtest: 2 page read speed is 3164 KiB/s mtd_speedtest: Testing erase speed mtd_speedtest: erase speed is 145441 KiB/s mtd_speedtest: Testing 2x multi-block erase speed mtd_speedtest: 2x multi-block erase speed is 146711 KiB/s mtd_speedtest: Testing 4x multi-block erase speed mtd_speedtest: 4x multi-block erase speed is 147139 KiB/s mtd_speedtest: Testing 8x multi-block erase speed mtd_speedtest: 8x multi-block erase speed is 147786 KiB/s mtd_speedtest: Testing 16x multi-block erase speed mtd_speedtest: 16x multi-block erase speed is 147569 KiB/s mtd_speedtest: Testing 32x multi-block erase speed mtd_speedtest: 32x multi-block erase speed is 147677 KiB/s mtd_speedtest: Testing 64x multi-block erase speed mtd_speedtest: 64x multi-block erase speed is 147677 KiB/s mtd_speedtest: finished ================================================= The test result enable the EDO mode: ================================================= mtd_speedtest: MTD device: 2 mtd_speedtest: MTD device size 209715200, eraseblock size 524288, page size 4096, count of eraseblocks 400, pages per eraseblock 128, OOB size 218 mtd_speedtest: scanned 400 eraseblocks, 6 are bad mtd_speedtest: testing eraseblock write speed mtd_speedtest: eraseblock write speed is 3733 KiB/s mtd_speedtest: testing eraseblock read speed mtd_speedtest: eraseblock read speed is 20413 KiB/s mtd_speedtest: testing page write speed mtd_speedtest: page write speed is 3603 KiB/s mtd_speedtest: testing page read speed mtd_speedtest: page read speed is 18966 KiB/s mtd_speedtest: testing 2 page write speed mtd_speedtest: 2 page write speed is 3668 KiB/s mtd_speedtest: testing 2 page read speed mtd_speedtest: 2 page read speed is 19686 KiB/s mtd_speedtest: Testing erase speed mtd_speedtest: erase speed is 146604 KiB/s mtd_speedtest: Testing 2x multi-block erase speed mtd_speedtest: 2x multi-block erase speed is 147354 KiB/s mtd_speedtest: Testing 4x multi-block erase speed mtd_speedtest: 4x multi-block erase speed is 147677 KiB/s mtd_speedtest: Testing 8x multi-block erase speed mtd_speedtest: 8x multi-block erase speed is 148002 KiB/s mtd_speedtest: Testing 16x multi-block erase speed mtd_speedtest: 16x multi-block erase speed is 147894 KiB/s mtd_speedtest: Testing 32x multi-block erase speed mtd_speedtest: 32x multi-block erase speed is 148329 KiB/s mtd_speedtest: Testing 64x multi-block erase speed mtd_speedtest: 64x multi-block erase speed is 148220 KiB/s mtd_speedtest: finished ================================================= We can see that there is 6 times performance improvement for reading when we enable the EDO mode. Signed-off-by: Huang Shijie --- drivers/mtd/nand/gpmi-nand/gpmi-lib.c | 164 ++++++++++++++++++++++++++++++++- drivers/mtd/nand/gpmi-nand/gpmi-nand.h | 4 + 2 files changed, 166 insertions(+), 2 deletions(-) diff --git a/drivers/mtd/nand/gpmi-nand/gpmi-lib.c b/drivers/mtd/nand/gpmi-nand/gpmi-lib.c index c13ae42a137d..01c81faffa4a 100644 --- a/drivers/mtd/nand/gpmi-nand/gpmi-lib.c +++ b/drivers/mtd/nand/gpmi-nand/gpmi-lib.c @@ -30,6 +30,9 @@ #define FEATURE_SIZE 4 /* p1, p2, p3, p4 */ #define NAND_CMD_SET_FEATURE 0xef +#define NAND_CMD_GET_FEATURE 0xee +#define ONFI_ASYNC_MODE_4 (1 << 4) +#define ONFI_ASYNC_MODE_5 (1 << 5) struct timing_threshod timing_default_threshold = { .max_data_setup_cycles = (BM_GPMI_TIMING0_DATA_SETUP >> @@ -308,6 +311,139 @@ inline bool is_ddr_nand(struct gpmi_nand_data *this) return this->nand.onfi_version; } +/* + * Firstly, we should know what's the GPMI-clock-period means. + * The GPMI-clock-period is the internal clock in the gpmi nand controller. + * If you set 100MHz to gpmi nand controller, the GPMI-clock-period is 10ns. + * + * Now, we begin to describe how to compute the right RDN_DELAY. + * + * 1) From the aspect of the nand chip pin: + * Delay = (tREA + C - tRP) [1] + * + * tREA : the maximum read access time. From the ONFI nand standards, + * we know that tREA is 16ns in mode 5, tREA is 20ns is mode 4. + * Please check it in : www.onfi.org + * C : a constant for adjust the delay. Choose 4 or 3. + * tRP : the read pulse width. + * Specified by the HW_GPMI_TIMING0:DATA_SETUP: + * tRP = (GPMI-clock-period) * DATA_SETUP + * + * 2) From the aspect of the GPMI nand controller: + * Delay = RDN_DELAY * 0.125 * RP [2] + * + * RP : the DLL reference period. + * if (GPMI-clock-period > 12ns) + * RP = GPMI-clock-period / 2; + * else + * RP = GPMI-clock-period; + * + * Set the HW_GPMI_CTRL1:HALF_PERIOD if GPMI-clock-period + * is greater 12ns. + * + * 3) since [1] equals [2], we get: + * + * (tREA + 4 - tRP) * 8 + * RDN_DELAY = --------------------- [3] + * RP + * + * 4) We only support the fastest asynchronous mode of ONFI nand. + * For some ONFI nand, the mode 4 is the fastest mode; + * while for some ONFI nand, the mode 5 is the fastest mode. + * So we only support the mode 4 and mode 5. It is no need to + * support other modes. + */ +static void set_edo_timing(struct gpmi_nand_data *this, + struct gpmi_nfc_hardware_timing *hw) +{ + int mode = this->timing_mode; + + /* for GPMI_HW_GPMI_TIMING0 */ + hw->data_setup_in_cycles = 1; + hw->data_hold_in_cycles = 1; + hw->address_setup_in_cycles = ((5 == mode) ? 1 : 0); + + /* for GPMI_HW_GPMI_TIMING1 */ + hw->device_busy_timeout = 0x9000; + + /* for GPMI_HW_GPMI_CTRL1 */ + hw->wrn_dly_sel = 3; /* no delay for write strobe. */ + if (GPMI_IS_MX6Q(this)) { + /* + * for mx6q, we give a parameter table about the delay: + * delay | half_period + * + -------------------------+ + * (mode 5) | 8 | 0 | + * ------ | -------------------------+ + * (mode 4) | 0xe | 1 | + * + -------------------------+ + */ + if (mode == 5) { + hw->sample_delay_factor = 8; + hw->use_half_periods = 0; + } else /* mode == 4 */{ + hw->sample_delay_factor = 0xe; + hw->use_half_periods = 1; + } + } +} + +static int enable_edo_mode(struct gpmi_nand_data *this, int mode) +{ + struct resources *r = &this->resources; + struct nand_chip *nand = &this->nand; + struct mtd_info *mtd = &this->mtd; + uint8_t device_feature[FEATURE_SIZE]; + int status; + + nand->select_chip(mtd, 0); + + /* [1] send SET FEATURE commond to NAND */ + memset(device_feature, 0, sizeof(device_feature)); + device_feature[0] = mode & 0x7; + + nand->cmdfunc(mtd, NAND_CMD_RESET, -1, -1); + nand->cmdfunc(mtd, NAND_CMD_SET_FEATURE, 1, -1); + nand->write_buf(mtd, device_feature, FEATURE_SIZE); + status = nand->waitfunc(mtd, nand); + if (status & 1) { + printk(KERN_ERR "fail !!\n"); + return -EINVAL; + } + + memset(device_feature, 0, 4); + nand->cmdfunc(mtd, NAND_CMD_GET_FEATURE, 1, -1); + nand->read_buf(mtd, device_feature, 4); + if (device_feature[0] != mode) { + printk(KERN_ERR "failed in set feature of mode : %d\n", mode); + return -EINVAL; + + } + + nand->select_chip(mtd, -1); + + /* [2] about the clock, pay attention! */ + { + unsigned long rate; + struct clk *enfc_clk; + + enfc_clk = clk_get(NULL, "enfc_clk"); + if (IS_ERR(enfc_clk)) { + printk(KERN_INFO "No enfc_clk clock\n"); + return -EINVAL; + } + + clk_set_parent(r->clock, enfc_clk); + rate = (mode == 5) ? 100000000 : 80000000; + clk_set_rate(enfc_clk, enfc_clk->round_rate(enfc_clk, rate)); + clk_set_rate(r->clock, rate); + } + + this->flags |= ASYNC_EDO_ENABLED; + this->timing_mode = mode; + dev_info(this->dev, "enable asynchronous EDO mode %d\n", mode); + return 0; +} /* To check if we need to initialize something else*/ int extra_init(struct gpmi_nand_data *this) { @@ -317,6 +453,24 @@ int extra_init(struct gpmi_nand_data *this) if (0) return enable_ddr_toggle(this); } + + /* Enable the asynchronous EDO mode, we only support the mode 4 and 5 */ + if (is_ddr_nand(this)) { + struct nand_chip *nand = &this->nand; + int mode; + + mode = le16_to_cpu(nand->onfi_params.async_timing_mode); + + if (mode & ONFI_ASYNC_MODE_5) + mode = 5; + else if (mode & ONFI_ASYNC_MODE_4) + mode = 4; + else + return 0; + + return enable_edo_mode(this, mode); + } + return 0; } @@ -1023,8 +1177,14 @@ void gpmi_begin(struct gpmi_nand_data *this) } } - /* calculate the timings. */ - gpmi_nfc_compute_hardware_timing(this, &hw); + if (this->flags & ASYNC_EDO_ENABLED) { + if (this->flags & ASYNC_EDO_TIMING_CONFIGED) + return; + set_edo_timing(this, &hw); + this->flags |= ASYNC_EDO_TIMING_CONFIGED; + } else { + gpmi_nfc_compute_hardware_timing(this, &hw); + } /* [1] Set HW_GPMI_TIMING0 */ reg = BF_GPMI_TIMING0_ADDRESS_SETUP(hw.address_setup_in_cycles) | diff --git a/drivers/mtd/nand/gpmi-nand/gpmi-nand.h b/drivers/mtd/nand/gpmi-nand/gpmi-nand.h index 19b049f64d60..21e1f6569211 100644 --- a/drivers/mtd/nand/gpmi-nand/gpmi-nand.h +++ b/drivers/mtd/nand/gpmi-nand/gpmi-nand.h @@ -120,6 +120,8 @@ struct nand_timing { int8_t tRHOH_in_ns; }; +#define ASYNC_EDO_ENABLED 1 +#define ASYNC_EDO_TIMING_CONFIGED 2 struct gpmi_nand_data { /* System Interface */ struct device *dev; @@ -181,6 +183,8 @@ struct gpmi_nand_data { /* private */ void *private; + int flags; + int timing_mode; }; /** -- cgit v1.2.3