* [PATCH 1/8] mtd: nand: mxc: reorder functions to avoid forward declarations
2018-01-17 11:32 [PATCH v2] mxc_nand controller fixes Sascha Hauer
@ 2018-01-17 11:32 ` Sascha Hauer
2018-01-17 11:32 ` [PATCH 2/8] mtd: nand: mxc: Add function to control hardware ECC Sascha Hauer
` (7 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Sascha Hauer @ 2018-01-17 11:32 UTC (permalink / raw)
To: linux-mtd; +Cc: Boris Brezillon, Richard Weinberger, kernel, Sascha Hauer
We'll call copy_spare() and mxc_do_addr_cycle() from another place
during the next patches, so move functions up to avoid forward
declarations.
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
drivers/mtd/nand/mxc_nand.c | 206 ++++++++++++++++++++++----------------------
1 file changed, 103 insertions(+), 103 deletions(-)
diff --git a/drivers/mtd/nand/mxc_nand.c b/drivers/mtd/nand/mxc_nand.c
index f3be0b2a8869..6741af990dc4 100644
--- a/drivers/mtd/nand/mxc_nand.c
+++ b/drivers/mtd/nand/mxc_nand.c
@@ -252,6 +252,109 @@ static void memcpy16_toio(void __iomem *trg, const void *src, int size)
__raw_writew(*s++, t++);
}
+/*
+ * The controller splits a page into data chunks of 512 bytes + partial oob.
+ * There are writesize / 512 such chunks, the size of the partial oob parts is
+ * oobsize / #chunks rounded down to a multiple of 2. The last oob chunk then
+ * contains additionally the byte lost by rounding (if any).
+ * This function handles the needed shuffling between host->data_buf (which
+ * holds a page in natural order, i.e. writesize bytes data + oobsize bytes
+ * spare) and the NFC buffer.
+ */
+static void copy_spare(struct mtd_info *mtd, bool bfrom)
+{
+ struct nand_chip *this = mtd_to_nand(mtd);
+ struct mxc_nand_host *host = nand_get_controller_data(this);
+ u16 i, oob_chunk_size;
+ u16 num_chunks = mtd->writesize / 512;
+
+ u8 *d = host->data_buf + mtd->writesize;
+ u8 __iomem *s = host->spare0;
+ u16 sparebuf_size = host->devtype_data->spare_len;
+
+ /* size of oob chunk for all but possibly the last one */
+ oob_chunk_size = (host->used_oobsize / num_chunks) & ~1;
+
+ if (bfrom) {
+ for (i = 0; i < num_chunks - 1; i++)
+ memcpy16_fromio(d + i * oob_chunk_size,
+ s + i * sparebuf_size,
+ oob_chunk_size);
+
+ /* the last chunk */
+ memcpy16_fromio(d + i * oob_chunk_size,
+ s + i * sparebuf_size,
+ host->used_oobsize - i * oob_chunk_size);
+ } else {
+ for (i = 0; i < num_chunks - 1; i++)
+ memcpy16_toio(&s[i * sparebuf_size],
+ &d[i * oob_chunk_size],
+ oob_chunk_size);
+
+ /* the last chunk */
+ memcpy16_toio(&s[i * sparebuf_size],
+ &d[i * oob_chunk_size],
+ host->used_oobsize - i * oob_chunk_size);
+ }
+}
+
+/*
+ * MXC NANDFC can only perform full page+spare or spare-only read/write. When
+ * the upper layers perform a read/write buf operation, the saved column address
+ * is used to index into the full page. So usually this function is called with
+ * column == 0 (unless no column cycle is needed indicated by column == -1)
+ */
+static void mxc_do_addr_cycle(struct mtd_info *mtd, int column, int page_addr)
+{
+ struct nand_chip *nand_chip = mtd_to_nand(mtd);
+ struct mxc_nand_host *host = nand_get_controller_data(nand_chip);
+
+ /* Write out column address, if necessary */
+ if (column != -1) {
+ host->devtype_data->send_addr(host, column & 0xff,
+ page_addr == -1);
+ if (mtd->writesize > 512)
+ /* another col addr cycle for 2k page */
+ host->devtype_data->send_addr(host,
+ (column >> 8) & 0xff,
+ false);
+ }
+
+ /* Write out page address, if necessary */
+ if (page_addr != -1) {
+ /* paddr_0 - p_addr_7 */
+ host->devtype_data->send_addr(host, (page_addr & 0xff), false);
+
+ if (mtd->writesize > 512) {
+ if (mtd->size >= 0x10000000) {
+ /* paddr_8 - paddr_15 */
+ host->devtype_data->send_addr(host,
+ (page_addr >> 8) & 0xff,
+ false);
+ host->devtype_data->send_addr(host,
+ (page_addr >> 16) & 0xff,
+ true);
+ } else
+ /* paddr_8 - paddr_15 */
+ host->devtype_data->send_addr(host,
+ (page_addr >> 8) & 0xff, true);
+ } else {
+ if (nand_chip->options & NAND_ROW_ADDR_3) {
+ /* paddr_8 - paddr_15 */
+ host->devtype_data->send_addr(host,
+ (page_addr >> 8) & 0xff,
+ false);
+ host->devtype_data->send_addr(host,
+ (page_addr >> 16) & 0xff,
+ true);
+ } else
+ /* paddr_8 - paddr_15 */
+ host->devtype_data->send_addr(host,
+ (page_addr >> 8) & 0xff, true);
+ }
+ }
+}
+
static int check_int_v3(struct mxc_nand_host *host)
{
uint32_t tmp;
@@ -772,109 +875,6 @@ static void mxc_nand_select_chip_v2(struct mtd_info *mtd, int chip)
writew(host->active_cs << 4, NFC_V1_V2_BUF_ADDR);
}
-/*
- * The controller splits a page into data chunks of 512 bytes + partial oob.
- * There are writesize / 512 such chunks, the size of the partial oob parts is
- * oobsize / #chunks rounded down to a multiple of 2. The last oob chunk then
- * contains additionally the byte lost by rounding (if any).
- * This function handles the needed shuffling between host->data_buf (which
- * holds a page in natural order, i.e. writesize bytes data + oobsize bytes
- * spare) and the NFC buffer.
- */
-static void copy_spare(struct mtd_info *mtd, bool bfrom)
-{
- struct nand_chip *this = mtd_to_nand(mtd);
- struct mxc_nand_host *host = nand_get_controller_data(this);
- u16 i, oob_chunk_size;
- u16 num_chunks = mtd->writesize / 512;
-
- u8 *d = host->data_buf + mtd->writesize;
- u8 __iomem *s = host->spare0;
- u16 sparebuf_size = host->devtype_data->spare_len;
-
- /* size of oob chunk for all but possibly the last one */
- oob_chunk_size = (host->used_oobsize / num_chunks) & ~1;
-
- if (bfrom) {
- for (i = 0; i < num_chunks - 1; i++)
- memcpy16_fromio(d + i * oob_chunk_size,
- s + i * sparebuf_size,
- oob_chunk_size);
-
- /* the last chunk */
- memcpy16_fromio(d + i * oob_chunk_size,
- s + i * sparebuf_size,
- host->used_oobsize - i * oob_chunk_size);
- } else {
- for (i = 0; i < num_chunks - 1; i++)
- memcpy16_toio(&s[i * sparebuf_size],
- &d[i * oob_chunk_size],
- oob_chunk_size);
-
- /* the last chunk */
- memcpy16_toio(&s[i * sparebuf_size],
- &d[i * oob_chunk_size],
- host->used_oobsize - i * oob_chunk_size);
- }
-}
-
-/*
- * MXC NANDFC can only perform full page+spare or spare-only read/write. When
- * the upper layers perform a read/write buf operation, the saved column address
- * is used to index into the full page. So usually this function is called with
- * column == 0 (unless no column cycle is needed indicated by column == -1)
- */
-static void mxc_do_addr_cycle(struct mtd_info *mtd, int column, int page_addr)
-{
- struct nand_chip *nand_chip = mtd_to_nand(mtd);
- struct mxc_nand_host *host = nand_get_controller_data(nand_chip);
-
- /* Write out column address, if necessary */
- if (column != -1) {
- host->devtype_data->send_addr(host, column & 0xff,
- page_addr == -1);
- if (mtd->writesize > 512)
- /* another col addr cycle for 2k page */
- host->devtype_data->send_addr(host,
- (column >> 8) & 0xff,
- false);
- }
-
- /* Write out page address, if necessary */
- if (page_addr != -1) {
- /* paddr_0 - p_addr_7 */
- host->devtype_data->send_addr(host, (page_addr & 0xff), false);
-
- if (mtd->writesize > 512) {
- if (mtd->size >= 0x10000000) {
- /* paddr_8 - paddr_15 */
- host->devtype_data->send_addr(host,
- (page_addr >> 8) & 0xff,
- false);
- host->devtype_data->send_addr(host,
- (page_addr >> 16) & 0xff,
- true);
- } else
- /* paddr_8 - paddr_15 */
- host->devtype_data->send_addr(host,
- (page_addr >> 8) & 0xff, true);
- } else {
- if (nand_chip->options & NAND_ROW_ADDR_3) {
- /* paddr_8 - paddr_15 */
- host->devtype_data->send_addr(host,
- (page_addr >> 8) & 0xff,
- false);
- host->devtype_data->send_addr(host,
- (page_addr >> 16) & 0xff,
- true);
- } else
- /* paddr_8 - paddr_15 */
- host->devtype_data->send_addr(host,
- (page_addr >> 8) & 0xff, true);
- }
- }
-}
-
#define MXC_V1_ECCBYTES 5
static int mxc_v1_ooblayout_ecc(struct mtd_info *mtd, int section,
--
2.11.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH 2/8] mtd: nand: mxc: Add function to control hardware ECC
2018-01-17 11:32 [PATCH v2] mxc_nand controller fixes Sascha Hauer
2018-01-17 11:32 ` [PATCH 1/8] mtd: nand: mxc: reorder functions to avoid forward declarations Sascha Hauer
@ 2018-01-17 11:32 ` Sascha Hauer
2018-01-17 11:32 ` [PATCH 3/8] mtd: nand: mxc: Add buffer argument to copy_spare Sascha Hauer
` (6 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Sascha Hauer @ 2018-01-17 11:32 UTC (permalink / raw)
To: linux-mtd; +Cc: Boris Brezillon, Richard Weinberger, kernel, Sascha Hauer
For proper raw read/write support need to be able to control the
hardware ECC engine. Add a function to enable/disable it.
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
drivers/mtd/nand/mxc_nand.c | 42 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 42 insertions(+)
diff --git a/drivers/mtd/nand/mxc_nand.c b/drivers/mtd/nand/mxc_nand.c
index 6741af990dc4..019c58579dbc 100644
--- a/drivers/mtd/nand/mxc_nand.c
+++ b/drivers/mtd/nand/mxc_nand.c
@@ -154,6 +154,7 @@ struct mxc_nand_devtype_data {
u_char *read_ecc, u_char *calc_ecc);
int (*setup_data_interface)(struct mtd_info *mtd, int csline,
const struct nand_data_interface *conf);
+ void (*enable_hwecc)(struct nand_chip *chip, bool enable);
/*
* On i.MX21 the CONFIG2:INT bit cannot be read if interrupts are masked
@@ -678,6 +679,42 @@ static uint16_t get_dev_status_v1_v2(struct mxc_nand_host *host)
return ret;
}
+static void mxc_nand_enable_hwecc_v1_v2(struct nand_chip *chip, bool enable)
+{
+ struct mxc_nand_host *host = nand_get_controller_data(chip);
+ uint16_t config1;
+
+ if (chip->ecc.mode != NAND_ECC_HW)
+ return;
+
+ config1 = readw(NFC_V1_V2_CONFIG1);
+
+ if (enable)
+ config1 |= NFC_V1_V2_CONFIG1_ECC_EN;
+ else
+ config1 &= ~NFC_V1_V2_CONFIG1_ECC_EN;
+
+ writew(config1, NFC_V1_V2_CONFIG1);
+}
+
+static void mxc_nand_enable_hwecc_v3(struct nand_chip *chip, bool enable)
+{
+ struct mxc_nand_host *host = nand_get_controller_data(chip);
+ uint32_t config2;
+
+ if (chip->ecc.mode != NAND_ECC_HW)
+ return;
+
+ config2 = readl(NFC_V3_CONFIG2);
+
+ if (enable)
+ config2 |= NFC_V3_CONFIG2_ECC_EN;
+ else
+ config2 &= ~NFC_V3_CONFIG2_ECC_EN;
+
+ writel(config2, NFC_V3_CONFIG2);
+}
+
/* This functions is used by upper layer to checks if device is ready */
static int mxc_nand_dev_ready(struct mtd_info *mtd)
{
@@ -1408,6 +1445,7 @@ static const struct mxc_nand_devtype_data imx21_nand_devtype_data = {
.ooblayout = &mxc_v1_ooblayout_ops,
.select_chip = mxc_nand_select_chip_v1_v3,
.correct_data = mxc_nand_correct_data_v1,
+ .enable_hwecc = mxc_nand_enable_hwecc_v1_v2,
.irqpending_quirk = 1,
.needs_ip = 0,
.regs_offset = 0xe00,
@@ -1431,6 +1469,7 @@ static const struct mxc_nand_devtype_data imx27_nand_devtype_data = {
.ooblayout = &mxc_v1_ooblayout_ops,
.select_chip = mxc_nand_select_chip_v1_v3,
.correct_data = mxc_nand_correct_data_v1,
+ .enable_hwecc = mxc_nand_enable_hwecc_v1_v2,
.irqpending_quirk = 0,
.needs_ip = 0,
.regs_offset = 0xe00,
@@ -1456,6 +1495,7 @@ static const struct mxc_nand_devtype_data imx25_nand_devtype_data = {
.select_chip = mxc_nand_select_chip_v2,
.correct_data = mxc_nand_correct_data_v2_v3,
.setup_data_interface = mxc_nand_v2_setup_data_interface,
+ .enable_hwecc = mxc_nand_enable_hwecc_v1_v2,
.irqpending_quirk = 0,
.needs_ip = 0,
.regs_offset = 0x1e00,
@@ -1480,6 +1520,7 @@ static const struct mxc_nand_devtype_data imx51_nand_devtype_data = {
.ooblayout = &mxc_v2_ooblayout_ops,
.select_chip = mxc_nand_select_chip_v1_v3,
.correct_data = mxc_nand_correct_data_v2_v3,
+ .enable_hwecc = mxc_nand_enable_hwecc_v3,
.irqpending_quirk = 0,
.needs_ip = 1,
.regs_offset = 0,
@@ -1505,6 +1546,7 @@ static const struct mxc_nand_devtype_data imx53_nand_devtype_data = {
.ooblayout = &mxc_v2_ooblayout_ops,
.select_chip = mxc_nand_select_chip_v1_v3,
.correct_data = mxc_nand_correct_data_v2_v3,
+ .enable_hwecc = mxc_nand_enable_hwecc_v3,
.irqpending_quirk = 0,
.needs_ip = 1,
.regs_offset = 0,
--
2.11.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH 3/8] mtd: nand: mxc: Add buffer argument to copy_spare
2018-01-17 11:32 [PATCH v2] mxc_nand controller fixes Sascha Hauer
2018-01-17 11:32 ` [PATCH 1/8] mtd: nand: mxc: reorder functions to avoid forward declarations Sascha Hauer
2018-01-17 11:32 ` [PATCH 2/8] mtd: nand: mxc: Add function to control hardware ECC Sascha Hauer
@ 2018-01-17 11:32 ` Sascha Hauer
2018-01-17 11:32 ` [PATCH 4/8] mtd: nand: mxc: Fix failed/corrected values for v2/v3 controllers Sascha Hauer
` (5 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Sascha Hauer @ 2018-01-17 11:32 UTC (permalink / raw)
To: linux-mtd; +Cc: Boris Brezillon, Richard Weinberger, kernel, Sascha Hauer
With following patches we will have to copy the spare data to/from
other buffers, so add the buffer as argument to copy_spare().
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
drivers/mtd/nand/mxc_nand.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/drivers/mtd/nand/mxc_nand.c b/drivers/mtd/nand/mxc_nand.c
index 019c58579dbc..ab9cd45237d3 100644
--- a/drivers/mtd/nand/mxc_nand.c
+++ b/drivers/mtd/nand/mxc_nand.c
@@ -262,14 +262,14 @@ static void memcpy16_toio(void __iomem *trg, const void *src, int size)
* holds a page in natural order, i.e. writesize bytes data + oobsize bytes
* spare) and the NFC buffer.
*/
-static void copy_spare(struct mtd_info *mtd, bool bfrom)
+static void copy_spare(struct mtd_info *mtd, bool bfrom, void *buf)
{
struct nand_chip *this = mtd_to_nand(mtd);
struct mxc_nand_host *host = nand_get_controller_data(this);
u16 i, oob_chunk_size;
u16 num_chunks = mtd->writesize / 512;
- u8 *d = host->data_buf + mtd->writesize;
+ u8 *d = buf;
u8 __iomem *s = host->spare0;
u16 sparebuf_size = host->devtype_data->spare_len;
@@ -1295,7 +1295,7 @@ static void mxc_nand_command(struct mtd_info *mtd, unsigned command,
memcpy32_fromio(host->data_buf, host->main_area0,
mtd->writesize);
- copy_spare(mtd, true);
+ copy_spare(mtd, true, host->data_buf + mtd->writesize);
break;
case NAND_CMD_SEQIN:
@@ -1314,7 +1314,7 @@ static void mxc_nand_command(struct mtd_info *mtd, unsigned command,
case NAND_CMD_PAGEPROG:
memcpy32_toio(host->main_area0, host->data_buf, mtd->writesize);
- copy_spare(mtd, false);
+ copy_spare(mtd, false, host->data_buf + mtd->writesize);
host->devtype_data->send_page(mtd, NFC_INPUT);
host->devtype_data->send_cmd(host, command, true);
WARN_ONCE(column != -1 || page_addr != -1,
--
2.11.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH 4/8] mtd: nand: mxc: Fix failed/corrected values for v2/v3 controllers
2018-01-17 11:32 [PATCH v2] mxc_nand controller fixes Sascha Hauer
` (2 preceding siblings ...)
2018-01-17 11:32 ` [PATCH 3/8] mtd: nand: mxc: Add buffer argument to copy_spare Sascha Hauer
@ 2018-01-17 11:32 ` Sascha Hauer
2018-01-17 11:32 ` [PATCH 5/8] mtd: nand: mxc: Fix failed/corrected values for v1 controllers Sascha Hauer
` (4 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Sascha Hauer @ 2018-01-17 11:32 UTC (permalink / raw)
To: linux-mtd; +Cc: Boris Brezillon, Richard Weinberger, kernel, Sascha Hauer
Currently nand_read_page_hwecc() from nand_base calls
mxc_nand_correct_data_v2_v3() for each subpage, but in this function we
return the corrected/failed results for the whole page instead
of a single subpage. On a 2k page size Nand this leads to results which
are 4 times too high.
The whole ecc.calculate/ecc.correct mechanism used by
nand_read_page_hwecc() is not suitable for devices which correct the
data in hardware, so fix this by using a driver specific read_page
function which does the right thing. Also add read_page_raw and read_oob
For proper raw and oob read support.
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
drivers/mtd/nand/mxc_nand.c | 86 ++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 77 insertions(+), 9 deletions(-)
diff --git a/drivers/mtd/nand/mxc_nand.c b/drivers/mtd/nand/mxc_nand.c
index ab9cd45237d3..48804320056f 100644
--- a/drivers/mtd/nand/mxc_nand.c
+++ b/drivers/mtd/nand/mxc_nand.c
@@ -140,6 +140,8 @@ struct mxc_nand_host;
struct mxc_nand_devtype_data {
void (*preset)(struct mtd_info *);
+ int (*read_page)(struct nand_chip *chip, void *buf, void *oob, bool ecc,
+ int page);
void (*send_cmd)(struct mxc_nand_host *, uint16_t, int);
void (*send_addr)(struct mxc_nand_host *, uint16_t, int);
void (*send_page)(struct mtd_info *, unsigned int);
@@ -757,13 +759,35 @@ static int mxc_nand_correct_data_v1(struct mtd_info *mtd, u_char *dat,
static int mxc_nand_correct_data_v2_v3(struct mtd_info *mtd, u_char *dat,
u_char *read_ecc, u_char *calc_ecc)
{
- struct nand_chip *nand_chip = mtd_to_nand(mtd);
- struct mxc_nand_host *host = nand_get_controller_data(nand_chip);
+ return 0;
+}
+
+static int mxc_nand_read_page_v2_v3(struct nand_chip *chip, void *buf,
+ void *oob, bool ecc, int page)
+{
+ struct mtd_info *mtd = nand_to_mtd(chip);
+ struct mxc_nand_host *host = nand_get_controller_data(chip);
+ unsigned int max_bitflips = 0;
u32 ecc_stat, err;
- int no_subpages = 1;
- int ret = 0;
+ int no_subpages;
u8 ecc_bit_mask, err_limit;
+ host->devtype_data->enable_hwecc(chip, ecc);
+
+ host->devtype_data->send_cmd(host, NAND_CMD_READ0, false);
+ mxc_do_addr_cycle(mtd, 0, page);
+
+ if (mtd->writesize > 512)
+ host->devtype_data->send_cmd(host,
+ NAND_CMD_READSTART, true);
+
+ host->devtype_data->send_page(mtd, NFC_OUTPUT);
+
+ if (buf)
+ memcpy32_fromio(buf, host->main_area0, mtd->writesize);
+ if (oob)
+ copy_spare(mtd, true, oob);
+
ecc_bit_mask = (host->eccsize == 4) ? 0x7 : 0xf;
err_limit = (host->eccsize == 4) ? 0x4 : 0x8;
@@ -774,17 +798,53 @@ static int mxc_nand_correct_data_v2_v3(struct mtd_info *mtd, u_char *dat,
do {
err = ecc_stat & ecc_bit_mask;
if (err > err_limit) {
- dev_dbg(host->dev, "UnCorrectable RS-ECC Error\n");
- return -EBADMSG;
+ mtd->ecc_stats.failed++;
} else {
- ret += err;
+ mtd->ecc_stats.corrected += err;
+ max_bitflips = max_t(unsigned int, max_bitflips, err);
}
+
ecc_stat >>= 4;
} while (--no_subpages);
- dev_dbg(host->dev, "%d Symbol Correctable RS-ECC Error\n", ret);
+ return max_bitflips;
+}
- return ret;
+static int mxc_nand_read_page(struct mtd_info *mtd, struct nand_chip *chip,
+ uint8_t *buf, int oob_required, int page)
+{
+ struct mxc_nand_host *host = nand_get_controller_data(chip);
+ void *oob_buf;
+
+ if (oob_required)
+ oob_buf = chip->oob_poi;
+ else
+ oob_buf = NULL;
+
+ return host->devtype_data->read_page(chip, buf, oob_buf, 1, page);
+}
+
+static int mxc_nand_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
+ uint8_t *buf, int oob_required, int page)
+{
+ struct mxc_nand_host *host = nand_get_controller_data(chip);
+ void *oob_buf;
+
+ if (oob_required)
+ oob_buf = chip->oob_poi;
+ else
+ oob_buf = NULL;
+
+ return host->devtype_data->read_page(chip, buf, oob_buf, 0, page);
+}
+
+static int mxc_nand_read_oob(struct mtd_info *mtd, struct nand_chip *chip,
+ int page)
+{
+ struct mxc_nand_host *host = nand_get_controller_data(chip);
+
+ return host->devtype_data->read_page(chip, NULL, chip->oob_poi, 0,
+ page);
}
static int mxc_nand_calculate_ecc(struct mtd_info *mtd, const u_char *dat,
@@ -1483,6 +1543,7 @@ static const struct mxc_nand_devtype_data imx27_nand_devtype_data = {
/* v21: i.MX25, i.MX35 */
static const struct mxc_nand_devtype_data imx25_nand_devtype_data = {
.preset = preset_v2,
+ .read_page = mxc_nand_read_page_v2_v3,
.send_cmd = send_cmd_v1_v2,
.send_addr = send_addr_v1_v2,
.send_page = send_page_v2,
@@ -1509,6 +1570,7 @@ static const struct mxc_nand_devtype_data imx25_nand_devtype_data = {
/* v3.2a: i.MX51 */
static const struct mxc_nand_devtype_data imx51_nand_devtype_data = {
.preset = preset_v3,
+ .read_page = mxc_nand_read_page_v2_v3,
.send_cmd = send_cmd_v3,
.send_addr = send_addr_v3,
.send_page = send_page_v3,
@@ -1535,6 +1597,7 @@ static const struct mxc_nand_devtype_data imx51_nand_devtype_data = {
/* v3.2b: i.MX53 */
static const struct mxc_nand_devtype_data imx53_nand_devtype_data = {
.preset = preset_v3,
+ .read_page = mxc_nand_read_page_v2_v3,
.send_cmd = send_cmd_v3,
.send_addr = send_addr_v3,
.send_page = send_page_v3,
@@ -1793,6 +1856,11 @@ static int mxcnd_probe(struct platform_device *pdev)
switch (this->ecc.mode) {
case NAND_ECC_HW:
+ if (host->devtype_data->read_page) {
+ this->ecc.read_page = mxc_nand_read_page;
+ this->ecc.read_page_raw = mxc_nand_read_page_raw;
+ this->ecc.read_oob = mxc_nand_read_oob;
+ }
this->ecc.calculate = mxc_nand_calculate_ecc;
this->ecc.hwctl = mxc_nand_enable_hwecc;
this->ecc.correct = host->devtype_data->correct_data;
--
2.11.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH 5/8] mtd: nand: mxc: Fix failed/corrected values for v1 controllers
2018-01-17 11:32 [PATCH v2] mxc_nand controller fixes Sascha Hauer
` (3 preceding siblings ...)
2018-01-17 11:32 ` [PATCH 4/8] mtd: nand: mxc: Fix failed/corrected values for v2/v3 controllers Sascha Hauer
@ 2018-01-17 11:32 ` Sascha Hauer
2018-01-17 11:32 ` [PATCH 6/8] mtd: nand: mxc: Add own write_page Sascha Hauer
` (3 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Sascha Hauer @ 2018-01-17 11:32 UTC (permalink / raw)
To: linux-mtd; +Cc: Boris Brezillon, Richard Weinberger, kernel, Sascha Hauer
The v1 controller code has several flaws:
- We do not forward the number of corrected bitflips to the upper layers
- For 2k page NAND chips only the status results from the fourth subpage
read are evaluated, so ECC failures in the other subpages remain
uncovered
- When there are uncorrectable errors we have to increase the statistics
counter, but still have to return successfully. Currently we return
an error
This patch fixes this by introducing a v1 specific read_page function.
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
drivers/mtd/nand/mxc_nand.c | 77 +++++++++++++++++++++++++++++++++++----------
1 file changed, 60 insertions(+), 17 deletions(-)
diff --git a/drivers/mtd/nand/mxc_nand.c b/drivers/mtd/nand/mxc_nand.c
index 48804320056f..49885afbcf43 100644
--- a/drivers/mtd/nand/mxc_nand.c
+++ b/drivers/mtd/nand/mxc_nand.c
@@ -738,22 +738,65 @@ static void mxc_nand_enable_hwecc(struct mtd_info *mtd, int mode)
static int mxc_nand_correct_data_v1(struct mtd_info *mtd, u_char *dat,
u_char *read_ecc, u_char *calc_ecc)
{
- struct nand_chip *nand_chip = mtd_to_nand(mtd);
- struct mxc_nand_host *host = nand_get_controller_data(nand_chip);
+ return 0;
+}
- /*
- * 1-Bit errors are automatically corrected in HW. No need for
- * additional correction. 2-Bit errors cannot be corrected by
- * HW ECC, so we need to return failure
- */
- uint16_t ecc_status = get_ecc_status_v1(host);
+static int mxc_nand_read_page_v1(struct nand_chip *chip, void *buf, void *oob,
+ bool ecc, int page)
+{
+ struct mtd_info *mtd = nand_to_mtd(chip);
+ struct mxc_nand_host *host = nand_get_controller_data(chip);
+ unsigned int bitflips_corrected = 0;
+ int no_subpages;
+ int i;
+
+ host->devtype_data->enable_hwecc(chip, ecc);
+
+ host->devtype_data->send_cmd(host, NAND_CMD_READ0, false);
+ mxc_do_addr_cycle(mtd, 0, page);
+
+ if (mtd->writesize > 512)
+ host->devtype_data->send_cmd(host, NAND_CMD_READSTART, true);
+
+ no_subpages = mtd->writesize >> 9;
+
+ for (i = 0; i < no_subpages; i++) {
+ uint16_t ecc_stats;
- if (((ecc_status & 0x3) == 2) || ((ecc_status >> 2) == 2)) {
- dev_dbg(host->dev, "HWECC uncorrectable 2-bit ECC error\n");
- return -EBADMSG;
+ /* NANDFC buffer 0 is used for page read/write */
+ writew((host->active_cs << 4) | i, NFC_V1_V2_BUF_ADDR);
+
+ writew(NFC_OUTPUT, NFC_V1_V2_CONFIG2);
+
+ /* Wait for operation to complete */
+ wait_op_done(host, true);
+
+ ecc_stats = get_ecc_status_v1(host);
+
+ ecc_stats >>= 2;
+
+ if (buf && ecc) {
+ switch (ecc_stats & 0x3) {
+ case 0:
+ default:
+ break;
+ case 1:
+ mtd->ecc_stats.corrected++;
+ bitflips_corrected = 1;
+ break;
+ case 2:
+ mtd->ecc_stats.failed++;
+ break;
+ }
+ }
}
- return 0;
+ if (buf)
+ memcpy32_fromio(buf, host->main_area0, mtd->writesize);
+ if (oob)
+ copy_spare(mtd, true, oob);
+
+ return bitflips_corrected;
}
static int mxc_nand_correct_data_v2_v3(struct mtd_info *mtd, u_char *dat,
@@ -1494,6 +1537,7 @@ static struct nand_bbt_descr bbt_mirror_descr = {
/* v1 + irqpending_quirk: i.MX21 */
static const struct mxc_nand_devtype_data imx21_nand_devtype_data = {
.preset = preset_v1,
+ .read_page = mxc_nand_read_page_v1,
.send_cmd = send_cmd_v1_v2,
.send_addr = send_addr_v1_v2,
.send_page = send_page_v1,
@@ -1518,6 +1562,7 @@ static const struct mxc_nand_devtype_data imx21_nand_devtype_data = {
/* v1 + !irqpending_quirk: i.MX27, i.MX31 */
static const struct mxc_nand_devtype_data imx27_nand_devtype_data = {
.preset = preset_v1,
+ .read_page = mxc_nand_read_page_v1,
.send_cmd = send_cmd_v1_v2,
.send_addr = send_addr_v1_v2,
.send_page = send_page_v1,
@@ -1856,11 +1901,9 @@ static int mxcnd_probe(struct platform_device *pdev)
switch (this->ecc.mode) {
case NAND_ECC_HW:
- if (host->devtype_data->read_page) {
- this->ecc.read_page = mxc_nand_read_page;
- this->ecc.read_page_raw = mxc_nand_read_page_raw;
- this->ecc.read_oob = mxc_nand_read_oob;
- }
+ this->ecc.read_page = mxc_nand_read_page;
+ this->ecc.read_page_raw = mxc_nand_read_page_raw;
+ this->ecc.read_oob = mxc_nand_read_oob;
this->ecc.calculate = mxc_nand_calculate_ecc;
this->ecc.hwctl = mxc_nand_enable_hwecc;
this->ecc.correct = host->devtype_data->correct_data;
--
2.11.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH 6/8] mtd: nand: mxc: Add own write_page
2018-01-17 11:32 [PATCH v2] mxc_nand controller fixes Sascha Hauer
` (4 preceding siblings ...)
2018-01-17 11:32 ` [PATCH 5/8] mtd: nand: mxc: Fix failed/corrected values for v1 controllers Sascha Hauer
@ 2018-01-17 11:32 ` Sascha Hauer
2018-01-17 11:32 ` [PATCH 7/8] mtd: nand: mxc: Drop now unnecessary functions Sascha Hauer
` (2 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Sascha Hauer @ 2018-01-17 11:32 UTC (permalink / raw)
To: linux-mtd; +Cc: Boris Brezillon, Richard Weinberger, kernel, Sascha Hauer
Now that we have our own read_page function add a write_page function
for consistency aswell. This can be a lot easier than the generic
function since we do not have to iterate over subpages but can write
the whole page at once. Also add write_page_raw and write_oob for
proper raw and oob write support.
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
drivers/mtd/nand/mxc_nand.c | 47 +++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 47 insertions(+)
diff --git a/drivers/mtd/nand/mxc_nand.c b/drivers/mtd/nand/mxc_nand.c
index 49885afbcf43..2811102b0469 100644
--- a/drivers/mtd/nand/mxc_nand.c
+++ b/drivers/mtd/nand/mxc_nand.c
@@ -890,6 +890,50 @@ static int mxc_nand_read_oob(struct mtd_info *mtd, struct nand_chip *chip,
page);
}
+static int mxc_nand_write_page(struct nand_chip *chip, const uint8_t *buf,
+ bool ecc, int page)
+{
+ struct mtd_info *mtd = nand_to_mtd(chip);
+ struct mxc_nand_host *host = nand_get_controller_data(chip);
+
+ host->devtype_data->enable_hwecc(chip, ecc);
+
+ host->devtype_data->send_cmd(host, NAND_CMD_SEQIN, false);
+ mxc_do_addr_cycle(mtd, 0, page);
+
+ memcpy32_toio(host->main_area0, buf, mtd->writesize);
+ copy_spare(mtd, false, chip->oob_poi);
+
+ host->devtype_data->send_page(mtd, NFC_INPUT);
+ host->devtype_data->send_cmd(host, NAND_CMD_PAGEPROG, true);
+ mxc_do_addr_cycle(mtd, 0, page);
+
+ return 0;
+}
+
+static int mxc_nand_write_page_ecc(struct mtd_info *mtd, struct nand_chip *chip,
+ const uint8_t *buf, int oob_required,
+ int page)
+{
+ return mxc_nand_write_page(chip, buf, true, page);
+}
+
+static int mxc_nand_write_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
+ const uint8_t *buf, int oob_required, int page)
+{
+ return mxc_nand_write_page(chip, buf, false, page);
+}
+
+static int mxc_nand_write_oob(struct mtd_info *mtd, struct nand_chip *chip,
+ int page)
+{
+ struct mxc_nand_host *host = nand_get_controller_data(chip);
+
+ memset(host->data_buf, 0xff, mtd->writesize);
+
+ return mxc_nand_write_page(chip, host->data_buf, false, page);
+}
+
static int mxc_nand_calculate_ecc(struct mtd_info *mtd, const u_char *dat,
u_char *ecc_code)
{
@@ -1904,6 +1948,9 @@ static int mxcnd_probe(struct platform_device *pdev)
this->ecc.read_page = mxc_nand_read_page;
this->ecc.read_page_raw = mxc_nand_read_page_raw;
this->ecc.read_oob = mxc_nand_read_oob;
+ this->ecc.write_page = mxc_nand_write_page_ecc;
+ this->ecc.write_page_raw = mxc_nand_write_page_raw;
+ this->ecc.write_oob = mxc_nand_write_oob;
this->ecc.calculate = mxc_nand_calculate_ecc;
this->ecc.hwctl = mxc_nand_enable_hwecc;
this->ecc.correct = host->devtype_data->correct_data;
--
2.11.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH 7/8] mtd: nand: mxc: Drop now unnecessary functions
2018-01-17 11:32 [PATCH v2] mxc_nand controller fixes Sascha Hauer
` (5 preceding siblings ...)
2018-01-17 11:32 ` [PATCH 6/8] mtd: nand: mxc: Add own write_page Sascha Hauer
@ 2018-01-17 11:32 ` Sascha Hauer
2018-01-17 11:32 ` [PATCH 8/8] mtd: nand: mxc: remove now unused code Sascha Hauer
2018-02-12 21:28 ` [PATCH v2] mxc_nand controller fixes Boris Brezillon
8 siblings, 0 replies; 10+ messages in thread
From: Sascha Hauer @ 2018-01-17 11:32 UTC (permalink / raw)
To: linux-mtd; +Cc: Boris Brezillon, Richard Weinberger, kernel, Sascha Hauer
Since we have our own read_page/write_page functions correct_data and
calculate are no longer needed. Remove them.
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
drivers/mtd/nand/mxc_nand.c | 36 ------------------------------------
1 file changed, 36 deletions(-)
diff --git a/drivers/mtd/nand/mxc_nand.c b/drivers/mtd/nand/mxc_nand.c
index 2811102b0469..a7c4fdd1988e 100644
--- a/drivers/mtd/nand/mxc_nand.c
+++ b/drivers/mtd/nand/mxc_nand.c
@@ -152,8 +152,6 @@ struct mxc_nand_devtype_data {
u32 (*get_ecc_status)(struct mxc_nand_host *);
const struct mtd_ooblayout_ops *ooblayout;
void (*select_chip)(struct mtd_info *mtd, int chip);
- int (*correct_data)(struct mtd_info *mtd, u_char *dat,
- u_char *read_ecc, u_char *calc_ecc);
int (*setup_data_interface)(struct mtd_info *mtd, int csline,
const struct nand_data_interface *conf);
void (*enable_hwecc)(struct nand_chip *chip, bool enable);
@@ -727,20 +725,6 @@ static int mxc_nand_dev_ready(struct mtd_info *mtd)
return 1;
}
-static void mxc_nand_enable_hwecc(struct mtd_info *mtd, int mode)
-{
- /*
- * If HW ECC is enabled, we turn it on during init. There is
- * no need to enable again here.
- */
-}
-
-static int mxc_nand_correct_data_v1(struct mtd_info *mtd, u_char *dat,
- u_char *read_ecc, u_char *calc_ecc)
-{
- return 0;
-}
-
static int mxc_nand_read_page_v1(struct nand_chip *chip, void *buf, void *oob,
bool ecc, int page)
{
@@ -799,12 +783,6 @@ static int mxc_nand_read_page_v1(struct nand_chip *chip, void *buf, void *oob,
return bitflips_corrected;
}
-static int mxc_nand_correct_data_v2_v3(struct mtd_info *mtd, u_char *dat,
- u_char *read_ecc, u_char *calc_ecc)
-{
- return 0;
-}
-
static int mxc_nand_read_page_v2_v3(struct nand_chip *chip, void *buf,
void *oob, bool ecc, int page)
{
@@ -934,12 +912,6 @@ static int mxc_nand_write_oob(struct mtd_info *mtd, struct nand_chip *chip,
return mxc_nand_write_page(chip, host->data_buf, false, page);
}
-static int mxc_nand_calculate_ecc(struct mtd_info *mtd, const u_char *dat,
- u_char *ecc_code)
-{
- return 0;
-}
-
static u_char mxc_nand_read_byte(struct mtd_info *mtd)
{
struct nand_chip *nand_chip = mtd_to_nand(mtd);
@@ -1592,7 +1564,6 @@ static const struct mxc_nand_devtype_data imx21_nand_devtype_data = {
.get_ecc_status = get_ecc_status_v1,
.ooblayout = &mxc_v1_ooblayout_ops,
.select_chip = mxc_nand_select_chip_v1_v3,
- .correct_data = mxc_nand_correct_data_v1,
.enable_hwecc = mxc_nand_enable_hwecc_v1_v2,
.irqpending_quirk = 1,
.needs_ip = 0,
@@ -1617,7 +1588,6 @@ static const struct mxc_nand_devtype_data imx27_nand_devtype_data = {
.get_ecc_status = get_ecc_status_v1,
.ooblayout = &mxc_v1_ooblayout_ops,
.select_chip = mxc_nand_select_chip_v1_v3,
- .correct_data = mxc_nand_correct_data_v1,
.enable_hwecc = mxc_nand_enable_hwecc_v1_v2,
.irqpending_quirk = 0,
.needs_ip = 0,
@@ -1643,7 +1613,6 @@ static const struct mxc_nand_devtype_data imx25_nand_devtype_data = {
.get_ecc_status = get_ecc_status_v2,
.ooblayout = &mxc_v2_ooblayout_ops,
.select_chip = mxc_nand_select_chip_v2,
- .correct_data = mxc_nand_correct_data_v2_v3,
.setup_data_interface = mxc_nand_v2_setup_data_interface,
.enable_hwecc = mxc_nand_enable_hwecc_v1_v2,
.irqpending_quirk = 0,
@@ -1670,7 +1639,6 @@ static const struct mxc_nand_devtype_data imx51_nand_devtype_data = {
.get_ecc_status = get_ecc_status_v3,
.ooblayout = &mxc_v2_ooblayout_ops,
.select_chip = mxc_nand_select_chip_v1_v3,
- .correct_data = mxc_nand_correct_data_v2_v3,
.enable_hwecc = mxc_nand_enable_hwecc_v3,
.irqpending_quirk = 0,
.needs_ip = 1,
@@ -1697,7 +1665,6 @@ static const struct mxc_nand_devtype_data imx53_nand_devtype_data = {
.get_ecc_status = get_ecc_status_v3,
.ooblayout = &mxc_v2_ooblayout_ops,
.select_chip = mxc_nand_select_chip_v1_v3,
- .correct_data = mxc_nand_correct_data_v2_v3,
.enable_hwecc = mxc_nand_enable_hwecc_v3,
.irqpending_quirk = 0,
.needs_ip = 1,
@@ -1951,9 +1918,6 @@ static int mxcnd_probe(struct platform_device *pdev)
this->ecc.write_page = mxc_nand_write_page_ecc;
this->ecc.write_page_raw = mxc_nand_write_page_raw;
this->ecc.write_oob = mxc_nand_write_oob;
- this->ecc.calculate = mxc_nand_calculate_ecc;
- this->ecc.hwctl = mxc_nand_enable_hwecc;
- this->ecc.correct = host->devtype_data->correct_data;
break;
case NAND_ECC_SOFT:
--
2.11.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH 8/8] mtd: nand: mxc: remove now unused code
2018-01-17 11:32 [PATCH v2] mxc_nand controller fixes Sascha Hauer
` (6 preceding siblings ...)
2018-01-17 11:32 ` [PATCH 7/8] mtd: nand: mxc: Drop now unnecessary functions Sascha Hauer
@ 2018-01-17 11:32 ` Sascha Hauer
2018-02-12 21:28 ` [PATCH v2] mxc_nand controller fixes Boris Brezillon
8 siblings, 0 replies; 10+ messages in thread
From: Sascha Hauer @ 2018-01-17 11:32 UTC (permalink / raw)
To: linux-mtd; +Cc: Boris Brezillon, Richard Weinberger, kernel, Sascha Hauer
Since we now have our own read_page/write_page functions
mxc_nand_command() will no longer be called with NAND_CMD_READ0,
NAND_CMD_READOOB, NAND_CMD_SEQIN and NAND_CMD_PAGEPROG. Remove
the code handling these commands.
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
drivers/mtd/nand/mxc_nand.c | 51 ---------------------------------------------
1 file changed, 51 deletions(-)
diff --git a/drivers/mtd/nand/mxc_nand.c b/drivers/mtd/nand/mxc_nand.c
index a7c4fdd1988e..87b5ee66e501 100644
--- a/drivers/mtd/nand/mxc_nand.c
+++ b/drivers/mtd/nand/mxc_nand.c
@@ -1391,57 +1391,6 @@ static void mxc_nand_command(struct mtd_info *mtd, unsigned command,
mxc_do_addr_cycle(mtd, column, page_addr);
break;
- case NAND_CMD_READ0:
- case NAND_CMD_READOOB:
- if (command == NAND_CMD_READ0)
- host->buf_start = column;
- else
- host->buf_start = column + mtd->writesize;
-
- command = NAND_CMD_READ0; /* only READ0 is valid */
-
- host->devtype_data->send_cmd(host, command, false);
- WARN_ONCE(column < 0,
- "Unexpected column/row value (cmd=%u, col=%d, row=%d)\n",
- command, column, page_addr);
- mxc_do_addr_cycle(mtd, 0, page_addr);
-
- if (mtd->writesize > 512)
- host->devtype_data->send_cmd(host,
- NAND_CMD_READSTART, true);
-
- host->devtype_data->send_page(mtd, NFC_OUTPUT);
-
- memcpy32_fromio(host->data_buf, host->main_area0,
- mtd->writesize);
- copy_spare(mtd, true, host->data_buf + mtd->writesize);
- break;
-
- case NAND_CMD_SEQIN:
- if (column >= mtd->writesize)
- /* call ourself to read a page */
- mxc_nand_command(mtd, NAND_CMD_READ0, 0, page_addr);
-
- host->buf_start = column;
-
- host->devtype_data->send_cmd(host, command, false);
- WARN_ONCE(column < -1,
- "Unexpected column/row value (cmd=%u, col=%d, row=%d)\n",
- command, column, page_addr);
- mxc_do_addr_cycle(mtd, 0, page_addr);
- break;
-
- case NAND_CMD_PAGEPROG:
- memcpy32_toio(host->main_area0, host->data_buf, mtd->writesize);
- copy_spare(mtd, false, host->data_buf + mtd->writesize);
- host->devtype_data->send_page(mtd, NFC_INPUT);
- host->devtype_data->send_cmd(host, command, true);
- WARN_ONCE(column != -1 || page_addr != -1,
- "Unexpected column/row value (cmd=%u, col=%d, row=%d)\n",
- command, column, page_addr);
- mxc_do_addr_cycle(mtd, column, page_addr);
- break;
-
case NAND_CMD_READID:
host->devtype_data->send_cmd(host, command, true);
mxc_do_addr_cycle(mtd, column, page_addr);
--
2.11.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* Re: [PATCH v2] mxc_nand controller fixes
2018-01-17 11:32 [PATCH v2] mxc_nand controller fixes Sascha Hauer
` (7 preceding siblings ...)
2018-01-17 11:32 ` [PATCH 8/8] mtd: nand: mxc: remove now unused code Sascha Hauer
@ 2018-02-12 21:28 ` Boris Brezillon
8 siblings, 0 replies; 10+ messages in thread
From: Boris Brezillon @ 2018-02-12 21:28 UTC (permalink / raw)
To: Sascha Hauer; +Cc: linux-mtd, Boris Brezillon, Richard Weinberger, kernel
On Wed, 17 Jan 2018 12:32:05 +0100
Sascha Hauer <s.hauer@pengutronix.de> wrote:
> This series fixes several problems in the mxc_nand driver.
>
> - Raw support does not work since hardware ECC is unconditionally
> enabled
> - The v2/v3 controller code returns the number of corrected
> bitflips for the whole page for each subpage leading to
> results for times too high
> - The v1 controller code returns the number of corrected bitflips
> only for the last subpage. On 2k page NANDs bitflips in the
> first three subpages remain uncovered
>
> This series fixes these issues. Tested with the mtd test modules
> mtd_nandbiterrs, mtd_oobtest, mtd_pagetest and mtd_readtest and the
> corresponding userspace tests from mtd-utils on a i.MX27 board which
> is a v1 controller and a i.MX25 board which is a v2 controller.
>
> Based on nand/next.
Applied the whole series.
Thanks,
Boris
>
> Sascha
>
> Changes since v1:
> - drop mtd argument from read_page/write_page functions
> - do not add function beginning with two underscores
> - simplify bitflips counting for v1 controllers
> - Add ecc argument to mxc_nand_write_page()
> - drop unused oob_required argument from mxc_nand_write_page()
>
--
Boris Brezillon, Bootlin (formerly Free Electrons)
Embedded Linux and Kernel engineering
http://bootlin.com
^ permalink raw reply [flat|nested] 10+ messages in thread