Hello,
This series adds support for the sunxi NAND Flash Controller (NFC).
This controller supports up to 8 NAND chip connected.
I'm still in the early stages drivers development and some key features are
missing, but it's usable (I tested it on the cubietruck board).
Here's what's missing:
- DMA support
- HW randomization support
- other improvements ?
This series depends on Emilio's patch series implementing mod0 clks
(http://lists.infradead.org/pipermail/linux-arm-kernel/2013-July/185478.html)
+ an other patch not yet posted
(http://git.elopez.com.ar/linux/commits/5b4eb3ac406b9c98965714d40e8dd6da943d1ab0)
Best Regards,
Boris
Changes since v1:
- add HW ECC support
- rework NAND timings retrieval (use ONFI timing mode instead of raw timings)
- add nand-ecc-level property to specify NAND ECC requirements from DT
Boris BREZILLON (14):
mtd: nand: retrieve ECC requirements from Hynix READ ID byte 4
of: mtd: add NAND ECC level requirements retrieval
of: mtd: add documentation for nand-ecc-level property
mtd: nand: define struct nand_timings
mtd: nand: add ONFI timing mode to nand_timings converter
of: mtd: add NAND timing mode retrieval support
of: mtd: add documentation for the ONFI NAND timing mode property
mtd: nand: add sunxi NAND flash controller support
mtd: nand: add sunxi NFC dt bindings doc
ARM: dt/sunxi: add NFC node to Allwinner A20 SoC
ARM: dt/sunxi: add NFC pinctrl pin definitions
ARM: sunxi/dt: enable NAND on cubietruck board
mtd: nand: add sunxi HW ECC support
ARM: sunxi/dt: enable HW ECC on cubietruck board
Documentation/devicetree/bindings/mtd/nand.txt | 8 +
.../devicetree/bindings/mtd/sunxi-nand.txt | 46 +
arch/arm/boot/dts/sun7i-a20-cubietruck.dts | 31 +
arch/arm/boot/dts/sun7i-a20.dtsi | 35 +
drivers/mtd/nand/Kconfig | 6 +
drivers/mtd/nand/Makefile | 3 +-
drivers/mtd/nand/nand_base.c | 37 +
drivers/mtd/nand/nand_timings.c | 248 +++++
drivers/mtd/nand/sunxi_nand.c | 997 ++++++++++++++++++++
drivers/of/of_mtd.c | 44 +
include/linux/mtd/nand.h | 53 ++
include/linux/of_mtd.h | 15 +
12 files changed, 1522 insertions(+), 1 deletion(-)
create mode 100644 Documentation/devicetree/bindings/mtd/sunxi-nand.txt
create mode 100644 drivers/mtd/nand/nand_timings.c
create mode 100644 drivers/mtd/nand/sunxi_nand.c
--
1.7.9.5
Some chip do not support automatic retrieval of ECC level requirements.
Provide an helper function to retrieve these requirements from DT.
Signed-off-by: Boris BREZILLON <[email protected]>
---
drivers/of/of_mtd.c | 25 +++++++++++++++++++++++++
include/linux/of_mtd.h | 7 +++++++
2 files changed, 32 insertions(+)
diff --git a/drivers/of/of_mtd.c b/drivers/of/of_mtd.c
index a27ec94..e8ced61 100644
--- a/drivers/of/of_mtd.c
+++ b/drivers/of/of_mtd.c
@@ -50,6 +50,31 @@ int of_get_nand_ecc_mode(struct device_node *np)
EXPORT_SYMBOL_GPL(of_get_nand_ecc_mode);
/**
+ * of_get_nand_ecc_level - Get nand ecc level for the given device_node
+ * @np: Pointer to the given device_node
+ * @strengh: ECC strength
+ * @blk_size: ECC block size
+ *
+ * The function gets ecc level requirements from property 'nand-ecc-level'.
+ * Return 0 on success, -errno otherwise.
+ */
+int of_get_nand_ecc_level(struct device_node *np, u32 *strengh, u32 *blk_size)
+{
+ int err;
+
+ err = of_property_read_u32_index(np, "nand-ecc-level", 0, strengh);
+ if (err < 0)
+ return err;
+
+ err = of_property_read_u32_index(np, "nand-ecc-level", 1, blk_size);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(of_get_nand_ecc_level);
+
+/**
* of_get_nand_bus_width - Get nand bus witdh for given device_node
* @np: Pointer to the given device_node
*
diff --git a/include/linux/of_mtd.h b/include/linux/of_mtd.h
index 6f10e93..3bd8c3b 100644
--- a/include/linux/of_mtd.h
+++ b/include/linux/of_mtd.h
@@ -13,6 +13,7 @@
#include <linux/of.h>
int of_get_nand_ecc_mode(struct device_node *np);
+int of_get_nand_ecc_level(struct device_node *np, u32 *strengh, u32 *blk_size);
int of_get_nand_bus_width(struct device_node *np);
bool of_get_nand_on_flash_bbt(struct device_node *np);
@@ -23,6 +24,12 @@ static inline int of_get_nand_ecc_mode(struct device_node *np)
return -ENOSYS;
}
+static inline int of_get_nand_ecc_level(struct device_node *np, u32 *strengh,
+ u32 *blk_size)
+{
+ return -ENOSYS;
+}
+
static inline int of_get_nand_bus_width(struct device_node *np)
{
return -ENOSYS;
--
1.7.9.5
Define a struct containing the standard NAND timings as described in NAND
datasheets.
Signed-off-by: Boris BREZILLON <[email protected]>
---
include/linux/mtd/nand.h | 49 ++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 49 insertions(+)
diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h
index 9e6c8f9..67f0829 100644
--- a/include/linux/mtd/nand.h
+++ b/include/linux/mtd/nand.h
@@ -805,4 +805,53 @@ static inline bool nand_is_slc(struct nand_chip *chip)
{
return chip->bits_per_cell == 1;
}
+
+/**
+ * struct nand_sdr_timings - SDR NAND chip timings
+ *
+ * This struct defines the timing requirements of a SDR NAND chip.
+ * These informations can be found in every NAND datasheets and the timings
+ * meaning are described in the ONFI specifications:
+ * http://www.onfi.org/~/media/ONFI/specs/onfi_3_1_spec.pdf (chapter 4.15 Timing
+ * Parameters)
+ *
+ */
+
+struct nand_sdr_timings {
+ u32 tALH_min;
+ u32 tADL_min;
+ u32 tALS_min;
+ u32 tAR_min;
+ u32 tCEA_max;
+ u32 tCEH_min;
+ u32 tCH_min;
+ u32 tCHZ_max;
+ u32 tCLH_min;
+ u32 tCLR_min;
+ u32 tCLS_min;
+ u32 tCOH_min;
+ u32 tCS_min;
+ u32 tDH_min;
+ u32 tDS_min;
+ u32 tFEAT_max;
+ u32 tIR_min;
+ u32 tITC_max;
+ u32 tRC_min;
+ u32 tREA_max;
+ u32 tREH_min;
+ u32 tRHOH_min;
+ u32 tRHW_min;
+ u32 tRHZ_max;
+ u32 tRLOH_min;
+ u32 tRP_min;
+ u32 tRR_min;
+ u64 tRST_max;
+ u32 tWB_max;
+ u32 tWC_min;
+ u32 tWH_min;
+ u32 tWHR_min;
+ u32 tWP_min;
+ u32 tWW_min;
+};
+
#endif /* __LINUX_MTD_NAND_H */
--
1.7.9.5
Add the sunxi NAND Flash Controller dt bindings documentation.
Signed-off-by: Boris BREZILLON <[email protected]>
---
.../devicetree/bindings/mtd/sunxi-nand.txt | 46 ++++++++++++++++++++
1 file changed, 46 insertions(+)
create mode 100644 Documentation/devicetree/bindings/mtd/sunxi-nand.txt
diff --git a/Documentation/devicetree/bindings/mtd/sunxi-nand.txt b/Documentation/devicetree/bindings/mtd/sunxi-nand.txt
new file mode 100644
index 0000000..b0e55a3
--- /dev/null
+++ b/Documentation/devicetree/bindings/mtd/sunxi-nand.txt
@@ -0,0 +1,46 @@
+Allwinner NAND Flash Controller (NFC)
+
+Required properties:
+- compatible : "allwinner,sun4i-nand".
+- reg : shall contain registers location and length for data and reg.
+- interrupts : shall define the nand controller interrupt.
+- #address-cells: shall be set to 1. Encode the nand CS.
+- #size-cells : shall be set to 0.
+- clocks : shall reference nand controller clocks.
+- clock-names : nand controller internal clock names. Shall contain :
+ * "ahb_clk" : AHB gating clock
+ * "sclk" : nand controller clock
+
+Optional children nodes:
+Children nodes represent the available nand chips.
+
+Optional properties:
+- onfi,nand-timing-mode : mandatory if the chip does not support the ONFI
+ standard.
+- allwinner,rb : shall contain the native Ready/Busy ids.
+ or
+- rb-gpios : shall contain the gpios used as R/B pins.
+
+see Documentation/devicetree/mtd/nand.txt for generic bindings.
+
+
+Examples:
+nfc: nand@01c03000 {
+ compatible = "allwinner,sun4i-nand";
+ reg = <0x01c03000 0x1000>;
+ interrupts = <0 37 1>;
+ clocks = <&ahb_gates 13>, <&nand_clk>;
+ clock-names = "ahb_clk", "sclk";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&nand_pins_a &nand_cs0_pins_a &nand_rb0_pins_a>;
+ status = "okay";
+
+ nand@0 {
+ reg = <0>;
+ allwinner,rb = <0>;
+ nand-ecc-mode = "soft_bch";
+ onfi,nand-timing-mode = <4>;
+ };
+};
--
1.7.9.5
Enable the NFC and describe the NAND flash connected to this controller.
Signed-off-by: Boris BREZILLON <[email protected]>
---
arch/arm/boot/dts/sun7i-a20-cubietruck.dts | 31 ++++++++++++++++++++++++++++
1 file changed, 31 insertions(+)
diff --git a/arch/arm/boot/dts/sun7i-a20-cubietruck.dts b/arch/arm/boot/dts/sun7i-a20-cubietruck.dts
index 8a1009d..031de97 100644
--- a/arch/arm/boot/dts/sun7i-a20-cubietruck.dts
+++ b/arch/arm/boot/dts/sun7i-a20-cubietruck.dts
@@ -19,6 +19,37 @@
compatible = "cubietech,cubietruck", "allwinner,sun7i-a20";
soc@01c00000 {
+ nfc: nand@01c03000 {
+ pinctrl-names = "default";
+ pinctrl-0 = <&nand_pins_a &nand_cs0_pins_a &nand_rb0_pins_a>;
+ status = "okay";
+
+ nand@0 {
+ #address-cells = <1>;
+ #size-cells = <1>;
+ reg = <0>;
+ allwinner,rb = <0>;
+ nand-ecc-mode = "soft_bch";
+
+ /* nand timings */
+ tCLS-min = <6>;
+ tCLH-min = <3>;
+ tCS-min = <20>;
+ tCH-min = <5>;
+ tWP-min = <8>;
+ tWH-min = <6>;
+ tALS-min = <6>;
+ tDS-min = <6>;
+ tDH-min = <2>;
+ tRR-min = <20>;
+ tALH-min = <3>;
+ tRP-min = <8>;
+ tREH-min = <6>;
+ tRC-min = <16>;
+ tWC-min = <16>;
+ };
+ };
+
pinctrl@01c20800 {
led_pins_cubietruck: led_pins@0 {
allwinner,pins = "PH7", "PH11", "PH20", "PH21";
--
1.7.9.5
Define the NAND pinctrl configs.
Signed-off-by: Boris BREZILLON <[email protected]>
---
arch/arm/boot/dts/sun7i-a20.dtsi | 24 ++++++++++++++++++++++++
1 file changed, 24 insertions(+)
diff --git a/arch/arm/boot/dts/sun7i-a20.dtsi b/arch/arm/boot/dts/sun7i-a20.dtsi
index 3b47253..0f6e002 100644
--- a/arch/arm/boot/dts/sun7i-a20.dtsi
+++ b/arch/arm/boot/dts/sun7i-a20.dtsi
@@ -389,6 +389,30 @@
allwinner,drive = <0>;
allwinner,pull = <0>;
};
+
+ nand_pins_a: nand_base0@0 {
+ allwinner,pins = "PC0", "PC1", "PC2",
+ "PC5", "PC8", "PC9", "PC10",
+ "PC11", "PC12", "PC13", "PC14",
+ "PC15", "PC16";
+ allwinner,function = "nand0";
+ allwinner,drive = <0>;
+ allwinner,pull = <0>;
+ };
+
+ nand_cs0_pins_a: nand_cs@0 {
+ allwinner,pins = "PC4";
+ allwinner,function = "nand0";
+ allwinner,drive = <0>;
+ allwinner,pull = <0>;
+ };
+
+ nand_rb0_pins_a: nand_rb@0 {
+ allwinner,pins = "PC6";
+ allwinner,function = "nand0";
+ allwinner,drive = <0>;
+ allwinner,pull = <0>;
+ };
};
timer@01c20c00 {
--
1.7.9.5
Signed-off-by: Boris BREZILLON <[email protected]>
---
arch/arm/boot/dts/sun7i-a20-cubietruck.dts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/arch/arm/boot/dts/sun7i-a20-cubietruck.dts b/arch/arm/boot/dts/sun7i-a20-cubietruck.dts
index 031de97..5828923 100644
--- a/arch/arm/boot/dts/sun7i-a20-cubietruck.dts
+++ b/arch/arm/boot/dts/sun7i-a20-cubietruck.dts
@@ -29,7 +29,7 @@
#size-cells = <1>;
reg = <0>;
allwinner,rb = <0>;
- nand-ecc-mode = "soft_bch";
+ nand-ecc-mode = "hw";
/* nand timings */
tCLS-min = <6>;
--
1.7.9.5
Add HW ECC support for the sunxi NAND Flash Controller.
Signed-off-by: Boris BREZILLON <[email protected]>
---
drivers/mtd/nand/sunxi_nand.c | 279 +++++++++++++++++++++++++++++++++++++++--
1 file changed, 266 insertions(+), 13 deletions(-)
diff --git a/drivers/mtd/nand/sunxi_nand.c b/drivers/mtd/nand/sunxi_nand.c
index d3da810..7e1cefc 100644
--- a/drivers/mtd/nand/sunxi_nand.c
+++ b/drivers/mtd/nand/sunxi_nand.c
@@ -163,6 +163,11 @@ struct sunxi_nand_chip_sel {
#define DEFAULT_NAME_FORMAT "nand@%d"
#define MAX_NAME_SIZE (sizeof("nand@") + 2)
+struct sunxi_nand_hw_ecc {
+ int mode;
+ struct nand_ecclayout layout;
+};
+
struct sunxi_nand_chip {
struct list_head node;
struct nand_chip nand;
@@ -402,6 +407,126 @@ static void sunxi_nfc_cmd_ctrl(struct mtd_info *mtd, int dat,
sunxi_nfc_wait_int(nfc, NFC_CMD_INT_FLAG, 0);
}
+static int sunxi_nfc_hwecc_read_page(struct mtd_info *mtd,
+ struct nand_chip *chip, uint8_t *buf,
+ int oob_required, int page)
+{
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(mtd);
+ struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller);
+ struct nand_ecc_ctrl *ecc = &chip->ecc;
+ struct nand_ecclayout *layout = ecc->layout;
+ struct sunxi_nand_hw_ecc *data = ecc->priv;
+ unsigned int max_bitflips = 0;
+ int offset;
+ u32 tmp;
+ int i;
+
+ tmp = readl(nfc->regs + NFC_REG_ECC_CTL);
+ tmp &= ~(NFC_ECC_MODE | NFC_ECC_PIPELINE | NFC_ECC_BLOCK_SIZE |
+ NFC_ECC_BLOCK_SIZE);
+ tmp |= NFC_ECC_EN | (data->mode << NFC_ECC_MODE_SHIFT);
+ writel(tmp, nfc->regs + NFC_REG_ECC_CTL);
+
+ for (i = 0; i < mtd->writesize / ecc->size; i++) {
+ if (i)
+ chip->cmdfunc(mtd, NAND_CMD_RNDOUT, i * ecc->size, -1);
+ chip->read_buf(mtd, NULL, chip->ecc.size);
+ offset = mtd->writesize + layout->eccpos[i * ecc->bytes] - 4;
+ chip->cmdfunc(mtd, NAND_CMD_RNDOUT, offset, -1);
+ while ((readl(nfc->regs + NFC_REG_ST) & NFC_CMD_FIFO_STATUS))
+ ;
+ tmp = NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD | (1 << 30);
+ writel(tmp, nfc->regs + NFC_REG_CMD);
+ sunxi_nfc_wait_int(nfc, NFC_CMD_INT_FLAG, 0);
+ memcpy_fromio(buf + (i * ecc->size), nfc->regs + NFC_RAM0_BASE,
+ chip->ecc.size);
+
+ if (readl(nfc->regs + NFC_REG_ECC_ST) & 0x1) {
+ mtd->ecc_stats.failed++;
+ } else {
+ tmp = readl(nfc->regs + NFC_REG_ECC_CNT0) & 0xff;
+ mtd->ecc_stats.corrected += tmp;
+ max_bitflips = max_t(unsigned int, max_bitflips, tmp);
+ }
+ }
+
+ if (oob_required) {
+ chip->cmdfunc(mtd, NAND_CMD_RNDOUT, mtd->writesize, -1);
+ chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
+ }
+
+ tmp = readl(nfc->regs + NFC_REG_ECC_CTL);
+ tmp &= ~NFC_ECC_EN;
+
+ writel(tmp, nfc->regs + NFC_REG_ECC_CTL);
+
+ return max_bitflips;
+}
+
+static int sunxi_nfc_hwecc_write_page(struct mtd_info *mtd,
+ struct nand_chip *chip,
+ const uint8_t *buf,
+ int oob_required)
+{
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(mtd);
+ struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller);
+ struct nand_ecc_ctrl *ecc = &chip->ecc;
+ struct nand_ecclayout *layout = ecc->layout;
+ struct sunxi_nand_hw_ecc *data = ecc->priv;
+ int offset;
+ u32 tmp;
+ int i;
+ int j;
+
+ tmp = readl(nfc->regs + NFC_REG_ECC_CTL);
+ tmp &= ~(NFC_ECC_MODE | NFC_ECC_PIPELINE | NFC_ECC_BLOCK_SIZE |
+ NFC_ECC_BLOCK_SIZE);
+ tmp |= NFC_ECC_EN | (data->mode << NFC_ECC_MODE_SHIFT);
+
+ writel(tmp, nfc->regs + NFC_REG_ECC_CTL);
+
+ for (i = 0; i < mtd->writesize / ecc->size; i++) {
+ if (i)
+ chip->cmdfunc(mtd, NAND_CMD_RNDIN, i * ecc->size, -1);
+
+ chip->write_buf(mtd, buf + (i * ecc->size), ecc->size);
+ offset = mtd->writesize + layout->eccpos[i * ecc->bytes] - 4;
+ chip->cmdfunc(mtd, NAND_CMD_RNDIN, offset, -1);
+ while ((readl(nfc->regs + NFC_REG_ST) & NFC_CMD_FIFO_STATUS))
+ ;
+
+ /* Fill OOB data in */
+ for (j = 0; j < 4; j++) {
+ if (oob_required) {
+ offset = layout->eccpos[i * ecc->size] - 4;
+ writeb(chip->oob_poi[offset + j],
+ nfc->regs + NFC_REG_USER_DATA_BASE + j);
+ } else {
+ writeb(0xff,
+ nfc->regs + NFC_REG_USER_DATA_BASE + j);
+ }
+ }
+
+ tmp = NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD |
+ NFC_ACCESS_DIR | (1 << 30);
+ writel(tmp, nfc->regs + NFC_REG_CMD);
+ sunxi_nfc_wait_int(nfc, NFC_CMD_INT_FLAG, 0);
+ }
+
+ if (oob_required && chip->ecc.layout->oobfree[0].length > 2) {
+ chip->cmdfunc(mtd, NAND_CMD_RNDIN, mtd->writesize, -1);
+ chip->write_buf(mtd, chip->oob_poi,
+ chip->ecc.layout->oobfree[0].length - 2);
+ }
+
+ tmp = readl(nfc->regs + NFC_REG_ECC_CTL);
+ tmp &= ~(NFC_ECC_EN | NFC_ECC_PIPELINE);
+
+ writel(tmp, nfc->regs + NFC_REG_ECC_CTL);
+
+ return 0;
+}
+
static int sunxi_nand_chip_init_timings(struct sunxi_nand_chip *chip,
struct device_node *np)
{
@@ -502,6 +627,144 @@ static int sunxi_nand_chip_init_timings(struct sunxi_nand_chip *chip,
return 0;
}
+static int sunxi_nand_chip_hwecc_init(struct device *dev,
+ struct sunxi_nand_chip *chip,
+ struct mtd_info *mtd,
+ struct device_node *np)
+{
+ struct nand_chip *nand = &chip->nand;
+ struct nand_ecc_ctrl *ecc = &nand->ecc;
+ struct sunxi_nand_hw_ecc *data;
+ struct nand_ecclayout *layout;
+ int nsectors;
+ int i;
+ int j;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ ecc->read_page = sunxi_nfc_hwecc_read_page;
+ ecc->write_page = sunxi_nfc_hwecc_write_page;
+
+ if (nand->ecc_strength_ds <= 16) {
+ nand->ecc_strength_ds = 16;
+ data->mode = 0;
+ } else if (nand->ecc_strength_ds <= 24) {
+ nand->ecc_strength_ds = 24;
+ data->mode = 1;
+ } else if (nand->ecc_strength_ds <= 28) {
+ nand->ecc_strength_ds = 28;
+ data->mode = 2;
+ } else if (nand->ecc_strength_ds <= 32) {
+ nand->ecc_strength_ds = 32;
+ data->mode = 3;
+ } else if (nand->ecc_strength_ds <= 40) {
+ nand->ecc_strength_ds = 40;
+ data->mode = 4;
+ } else if (nand->ecc_strength_ds <= 48) {
+ nand->ecc_strength_ds = 48;
+ data->mode = 5;
+ } else if (nand->ecc_strength_ds <= 56) {
+ nand->ecc_strength_ds = 56;
+ data->mode = 6;
+ } else if (nand->ecc_strength_ds <= 60) {
+ nand->ecc_strength_ds = 60;
+ data->mode = 7;
+ } else if (nand->ecc_strength_ds <= 64) {
+ nand->ecc_strength_ds = 64;
+ data->mode = 8;
+ } else {
+ dev_err(dev, "unsupported strength\n");
+ return -ENOTSUPP;
+ }
+
+ /* HW ECC always request ECC bytes for 1024 bytes blocks */
+ ecc->bytes = ((nand->ecc_strength_ds * fls(8 * 1024)) + 7) / 8;
+
+ /* HW ECC always work with even numbers of ECC bytes */
+ if (ecc->bytes % 2)
+ ecc->bytes++;
+ ecc->strength = nand->ecc_strength_ds;
+ ecc->size = nand->ecc_step_ds;
+
+ layout = &data->layout;
+ nsectors = mtd->writesize / ecc->size;
+
+ if (mtd->oobsize < ((ecc->bytes + 4) * nsectors))
+ return -EINVAL;
+
+ layout->eccbytes = (ecc->bytes * nsectors);
+
+ /*
+ * The first 2 bytes are used for BB markers.
+ * We merge the 4 user available bytes from HW ECC with this
+ * first section, hence why the + 2 operation (- 2 + 4).
+ */
+ layout->oobfree[0].length = mtd->oobsize + 2 -
+ ((ecc->bytes + 4) * nsectors);
+ layout->oobfree[0].offset = 2;
+ for (i = 0; i < nsectors; i++) {
+ /*
+ * The first 4 ECC block bytes are already counted in the first
+ * obbfree entry.
+ */
+ if (i) {
+ layout->oobfree[i].offset =
+ layout->oobfree[i - 1].offset +
+ layout->oobfree[i - 1].length +
+ ecc->bytes;
+ layout->oobfree[i].length = 4;
+ }
+
+ for (j = 0; j < ecc->bytes; j++)
+ layout->eccpos[(ecc->bytes * i) + j] =
+ layout->oobfree[i].offset +
+ layout->oobfree[i].length + j;
+ }
+
+ ecc->layout = layout;
+ ecc->priv = data;
+
+ return 0;
+}
+
+static int sunxi_nand_chip_ecc_init(struct device *dev,
+ struct sunxi_nand_chip *chip,
+ struct mtd_info *mtd,
+ struct device_node *np)
+{
+ struct nand_chip *nand = &chip->nand;
+ u32 strength;
+ u32 blk_size;
+ int ret;
+
+ nand->ecc.mode = of_get_nand_ecc_mode(np);
+
+ if (!of_get_nand_ecc_level(np, &strength, &blk_size)) {
+ nand->ecc_step_ds = blk_size;
+ nand->ecc_strength_ds = strength;
+ }
+
+ switch (nand->ecc.mode) {
+ case NAND_ECC_SOFT_BCH:
+ nand->ecc.size = nand->ecc_step_ds;
+ nand->ecc.bytes = ((nand->ecc_strength_ds *
+ fls(8 * nand->ecc_step_ds)) + 7) / 8;
+ break;
+ case NAND_ECC_HW:
+ ret = sunxi_nand_chip_hwecc_init(dev, chip, mtd, np);
+ if (ret)
+ return ret;
+ break;
+ case NAND_ECC_NONE:
+ default:
+ break;
+ }
+
+ return 0;
+}
+
static int sunxi_nand_chip_init(struct device *dev, struct sunxi_nfc *nfc,
struct device_node *np)
{
@@ -509,8 +772,6 @@ static int sunxi_nand_chip_init(struct device *dev, struct sunxi_nfc *nfc,
struct mtd_part_parser_data ppdata;
struct mtd_info *mtd;
struct nand_chip *nand;
- u32 strength;
- u32 blk_size;
int nsels;
int ret;
int i;
@@ -576,7 +837,6 @@ static int sunxi_nand_chip_init(struct device *dev, struct sunxi_nfc *nfc,
nand->write_buf = sunxi_nfc_write_buf;
nand->read_byte = sunxi_nfc_read_byte;
- nand->ecc.mode = of_get_nand_ecc_mode(np);
if (of_get_nand_on_flash_bbt(np))
nand->bbt_options |= NAND_BBT_USE_FLASH;
@@ -588,16 +848,9 @@ static int sunxi_nand_chip_init(struct device *dev, struct sunxi_nfc *nfc,
if (ret)
return ret;
- if (nand->ecc.mode == NAND_ECC_SOFT_BCH) {
- if (!of_get_nand_ecc_level(np, &strength, &blk_size)) {
- nand->ecc_step_ds = blk_size;
- nand->ecc_strength_ds = strength;
- }
-
- nand->ecc.size = nand->ecc_step_ds;
- nand->ecc.bytes = (((nand->ecc_strength_ds *
- fls(8 * nand->ecc_step_ds)) + 7) / 8);
- }
+ ret = sunxi_nand_chip_ecc_init(dev, chip, mtd, np);
+ if (ret)
+ return ret;
ret = nand_scan_tail(mtd);
if (ret)
--
1.7.9.5
Add a function to retrieve NAND timing mode (ONFI timing mode) from a given
DT node.
Signed-off-by: Boris BREZILLON <[email protected]>
---
drivers/of/of_mtd.c | 19 +++++++++++++++++++
include/linux/of_mtd.h | 8 ++++++++
2 files changed, 27 insertions(+)
diff --git a/drivers/of/of_mtd.c b/drivers/of/of_mtd.c
index e8ced61..63155d4 100644
--- a/drivers/of/of_mtd.c
+++ b/drivers/of/of_mtd.c
@@ -108,3 +108,22 @@ bool of_get_nand_on_flash_bbt(struct device_node *np)
return of_property_read_bool(np, "nand-on-flash-bbt");
}
EXPORT_SYMBOL_GPL(of_get_nand_on_flash_bbt);
+
+/**
+ * of_get_nand_timings - Get nand timings for the given device_node
+ * @np: Pointer to the given device_node
+ *
+ * return 0 on success errno other wise
+ */
+int of_get_nand_onfi_timing_mode(struct device_node *np)
+{
+ int err;
+ u32 mode;
+
+ err = of_property_read_u32(np, "onfi,nand-timing-mode", &mode);
+ if (err)
+ return err;
+
+ return mode;
+}
+EXPORT_SYMBOL_GPL(of_get_nand_onfi_timing_mode);
diff --git a/include/linux/of_mtd.h b/include/linux/of_mtd.h
index 3bd8c3b..eb9fda6 100644
--- a/include/linux/of_mtd.h
+++ b/include/linux/of_mtd.h
@@ -9,6 +9,8 @@
#ifndef __LINUX_OF_MTD_H
#define __LINUX_OF_NET_H
+#include <linux/mtd/nand.h>
+
#ifdef CONFIG_OF_MTD
#include <linux/of.h>
@@ -16,6 +18,7 @@ int of_get_nand_ecc_mode(struct device_node *np);
int of_get_nand_ecc_level(struct device_node *np, u32 *strengh, u32 *blk_size);
int of_get_nand_bus_width(struct device_node *np);
bool of_get_nand_on_flash_bbt(struct device_node *np);
+int of_get_nand_onfi_timing_mode(struct device_node *np);
#else /* CONFIG_OF_MTD */
@@ -40,6 +43,11 @@ static inline bool of_get_nand_on_flash_bbt(struct device_node *np)
return false;
}
+static inline int of_get_nand_onfi_timing_mode(struct device_node *np)
+{
+ return -ENOSYS;
+}
+
#endif /* CONFIG_OF_MTD */
#endif /* __LINUX_OF_MTD_H */
--
1.7.9.5
Add NAND Flash controller node definition to the A20 SoC.
Signed-off-by: Boris BREZILLON <[email protected]>
---
arch/arm/boot/dts/sun7i-a20.dtsi | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/arch/arm/boot/dts/sun7i-a20.dtsi b/arch/arm/boot/dts/sun7i-a20.dtsi
index 8e4cdcc..3b47253 100644
--- a/arch/arm/boot/dts/sun7i-a20.dtsi
+++ b/arch/arm/boot/dts/sun7i-a20.dtsi
@@ -299,6 +299,17 @@
#size-cells = <1>;
ranges;
+ nfc: nand@01c03000 {
+ compatible = "allwinner,sun4i-nand";
+ reg = <0x01c03000 0x1000>;
+ interrupts = <0 37 1>;
+ clocks = <&ahb_gates 13>, <&nand_clk>;
+ clock-names = "ahb_clk", "sclk";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
emac: ethernet@01c0b000 {
compatible = "allwinner,sun4i-emac";
reg = <0x01c0b000 0x1000>;
--
1.7.9.5
Add support for the sunxi NAND Flash Controller (NFC).
Signed-off-by: Boris BREZILLON <[email protected]>
---
drivers/mtd/nand/Kconfig | 6 +
drivers/mtd/nand/Makefile | 1 +
drivers/mtd/nand/sunxi_nand.c | 744 +++++++++++++++++++++++++++++++++++++++++
3 files changed, 751 insertions(+)
create mode 100644 drivers/mtd/nand/sunxi_nand.c
diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig
index 93ae6a6..784dd42 100644
--- a/drivers/mtd/nand/Kconfig
+++ b/drivers/mtd/nand/Kconfig
@@ -510,4 +510,10 @@ config MTD_NAND_XWAY
Enables support for NAND Flash chips on Lantiq XWAY SoCs. NAND is attached
to the External Bus Unit (EBU).
+config MTD_NAND_SUNXI
+ tristate "Support for NAND on Allwinner SoCs"
+ depends on ARCH_SUNXI
+ help
+ Enables support for NAND Flash chips on Allwinner SoCs.
+
endif # MTD_NAND
diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile
index bbea7a6..e3b4a34 100644
--- a/drivers/mtd/nand/Makefile
+++ b/drivers/mtd/nand/Makefile
@@ -49,5 +49,6 @@ obj-$(CONFIG_MTD_NAND_JZ4740) += jz4740_nand.o
obj-$(CONFIG_MTD_NAND_GPMI_NAND) += gpmi-nand/
obj-$(CONFIG_MTD_NAND_XWAY) += xway_nand.o
obj-$(CONFIG_MTD_NAND_BCM47XXNFLASH) += bcm47xxnflash/
+obj-$(CONFIG_MTD_NAND_SUNXI) += sunxi_nand.o
nand-objs := nand_base.o nand_bbt.o
diff --git a/drivers/mtd/nand/sunxi_nand.c b/drivers/mtd/nand/sunxi_nand.c
new file mode 100644
index 0000000..d3da810
--- /dev/null
+++ b/drivers/mtd/nand/sunxi_nand.c
@@ -0,0 +1,744 @@
+/*
+ * Copyright (C) 2013 Boris BREZILLON <[email protected]>
+ *
+ * Derived from:
+ * https://github.com/yuq/sunxi-nfc-mtd
+ * Copyright (C) 2013 Qiang Yu <[email protected]>
+ *
+ * https://github.com/hno/Allwinner-Info
+ * Copyright (C) 2013 Henrik Nordström <Henrik Nordström>
+ *
+ * Copyright (C) 2013 Dmitriy B. <[email protected]>
+ * Copyright (C) 2013 Sergey Lapin <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_gpio.h>
+#include <linux/of_mtd.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/partitions.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/dmaengine.h>
+#include <linux/gpio.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+
+#define NFC_REG_CTL 0x0000
+#define NFC_REG_ST 0x0004
+#define NFC_REG_INT 0x0008
+#define NFC_REG_TIMING_CTL 0x000C
+#define NFC_REG_TIMING_CFG 0x0010
+#define NFC_REG_ADDR_LOW 0x0014
+#define NFC_REG_ADDR_HIGH 0x0018
+#define NFC_REG_SECTOR_NUM 0x001C
+#define NFC_REG_CNT 0x0020
+#define NFC_REG_CMD 0x0024
+#define NFC_REG_RCMD_SET 0x0028
+#define NFC_REG_WCMD_SET 0x002C
+#define NFC_REG_IO_DATA 0x0030
+#define NFC_REG_ECC_CTL 0x0034
+#define NFC_REG_ECC_ST 0x0038
+#define NFC_REG_DEBUG 0x003C
+#define NFC_REG_ECC_CNT0 0x0040
+#define NFC_REG_ECC_CNT1 0x0044
+#define NFC_REG_ECC_CNT2 0x0048
+#define NFC_REG_ECC_CNT3 0x004c
+#define NFC_REG_USER_DATA_BASE 0x0050
+#define NFC_REG_SPARE_AREA 0x00A0
+#define NFC_RAM0_BASE 0x0400
+#define NFC_RAM1_BASE 0x0800
+
+/*define bit use in NFC_CTL*/
+#define NFC_EN (1 << 0)
+#define NFC_RESET (1 << 1)
+#define NFC_BUS_WIDYH (1 << 2)
+#define NFC_RB_SEL (1 << 3)
+#define NFC_CE_SEL (7 << 24)
+#define NFC_CE_CTL (1 << 6)
+#define NFC_CE_CTL1 (1 << 7)
+#define NFC_PAGE_SIZE (0xf << 8)
+#define NFC_SAM (1 << 12)
+#define NFC_RAM_METHOD (1 << 14)
+#define NFC_DEBUG_CTL (1 << 31)
+
+/*define bit use in NFC_ST*/
+#define NFC_RB_B2R (1 << 0)
+#define NFC_CMD_INT_FLAG (1 << 1)
+#define NFC_DMA_INT_FLAG (1 << 2)
+#define NFC_CMD_FIFO_STATUS (1 << 3)
+#define NFC_STA (1 << 4)
+#define NFC_NATCH_INT_FLAG (1 << 5)
+#define NFC_RB_STATE0 (1 << 8)
+#define NFC_RB_STATE1 (1 << 9)
+#define NFC_RB_STATE2 (1 << 10)
+#define NFC_RB_STATE3 (1 << 11)
+
+/*define bit use in NFC_INT*/
+#define NFC_B2R_INT_ENABLE (1 << 0)
+#define NFC_CMD_INT_ENABLE (1 << 1)
+#define NFC_DMA_INT_ENABLE (1 << 2)
+#define NFC_INT_MASK (NFC_B2R_INT_ENABLE | \
+ NFC_CMD_INT_ENABLE | \
+ NFC_DMA_INT_ENABLE)
+
+
+/*define bit use in NFC_CMD*/
+#define NFC_CMD_LOW_BYTE (0xff << 0)
+#define NFC_CMD_HIGH_BYTE (0xff << 8)
+#define NFC_ADR_NUM (0x7 << 16)
+#define NFC_SEND_ADR (1 << 19)
+#define NFC_ACCESS_DIR (1 << 20)
+#define NFC_DATA_TRANS (1 << 21)
+#define NFC_SEND_CMD1 (1 << 22)
+#define NFC_WAIT_FLAG (1 << 23)
+#define NFC_SEND_CMD2 (1 << 24)
+#define NFC_SEQ (1 << 25)
+#define NFC_DATA_SWAP_METHOD (1 << 26)
+#define NFC_ROW_AUTO_INC (1 << 27)
+#define NFC_SEND_CMD3 (1 << 28)
+#define NFC_SEND_CMD4 (1 << 29)
+#define NFC_CMD_TYPE (3 << 30)
+
+/* define bit use in NFC_RCMD_SET*/
+#define NFC_READ_CMD (0xff << 0)
+#define NFC_RANDOM_READ_CMD0 (0xff << 8)
+#define NFC_RANDOM_READ_CMD1 (0xff << 16)
+
+/*define bit use in NFC_WCMD_SET*/
+#define NFC_PROGRAM_CMD (0xff << 0)
+#define NFC_RANDOM_WRITE_CMD (0xff << 8)
+#define NFC_READ_CMD0 (0xff << 16)
+#define NFC_READ_CMD1 (0xff << 24)
+
+/*define bit use in NFC_ECC_CTL*/
+#define NFC_ECC_EN (1 << 0)
+#define NFC_ECC_PIPELINE (1 << 3)
+#define NFC_ECC_EXCEPTION (1 << 4)
+#define NFC_ECC_BLOCK_SIZE (1 << 5)
+#define NFC_RANDOM_EN (1 << 9)
+#define NFC_RANDOM_DIRECTION (1 << 10)
+#define NFC_ECC_MODE_SHIFT 12
+#define NFC_ECC_MODE (0xf << NFC_ECC_MODE_SHIFT)
+#define NFC_RANDOM_SEED (0x7fff << 16)
+
+
+
+enum sunxi_nand_rb_type {
+ RB_NONE,
+ RB_NATIVE,
+ RB_GPIO,
+};
+
+struct sunxi_nand_rb {
+ enum sunxi_nand_rb_type type;
+ union {
+ int gpio;
+ int nativeid;
+ } info;
+};
+
+struct sunxi_nand_chip_sel {
+ u8 cs;
+ struct sunxi_nand_rb rb;
+};
+
+#define DEFAULT_NAME_FORMAT "nand@%d"
+#define MAX_NAME_SIZE (sizeof("nand@") + 2)
+
+struct sunxi_nand_chip {
+ struct list_head node;
+ struct nand_chip nand;
+ struct mtd_info mtd;
+ char default_name[MAX_NAME_SIZE];
+ unsigned long clk_rate;
+ int selected;
+ int nsels;
+ struct sunxi_nand_chip_sel sels[0];
+};
+
+static inline struct sunxi_nand_chip *to_sunxi_nand(struct mtd_info *mtd)
+{
+ return container_of(mtd, struct sunxi_nand_chip, mtd);
+}
+
+struct sunxi_nfc {
+ struct nand_hw_control controller;
+ void __iomem *regs;
+ int irq;
+ struct clk *ahb_clk;
+ struct clk *sclk;
+ unsigned long assigned_cs;
+ unsigned long clk_rate;
+ struct list_head chips;
+ struct completion complete;
+};
+
+static inline struct sunxi_nfc *to_sunxi_nfc(struct nand_hw_control *ctrl)
+{
+ return container_of(ctrl, struct sunxi_nfc, controller);
+}
+
+static irqreturn_t sunxi_nfc_interrupt(int irq, void *dev_id)
+{
+ struct sunxi_nfc *nfc = dev_id;
+ u32 st = readl(nfc->regs + NFC_REG_ST);
+ u32 ien = readl(nfc->regs + NFC_REG_INT);
+
+ if (!(ien & st))
+ return IRQ_NONE;
+
+ if ((ien & st) == ien)
+ complete(&nfc->complete);
+
+ writel(st & NFC_INT_MASK, nfc->regs + NFC_REG_ST);
+ writel(~st & ien & NFC_INT_MASK, nfc->regs + NFC_REG_INT);
+
+ return IRQ_HANDLED;
+}
+
+static int sunxi_nfc_wait_int(struct sunxi_nfc *nfc, u32 flags,
+ unsigned int timeout_ms)
+{
+ init_completion(&nfc->complete);
+
+ writel(flags, nfc->regs + NFC_REG_INT);
+ if (!timeout_ms)
+ wait_for_completion(&nfc->complete);
+ else if (!wait_for_completion_timeout(&nfc->complete,
+ msecs_to_jiffies(timeout_ms)))
+ return -ETIMEDOUT;
+
+ return 0;
+}
+
+static int sunxi_nfc_dev_ready(struct mtd_info *mtd)
+{
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(mtd);
+ struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller);
+ struct sunxi_nand_rb *rb;
+ unsigned long timeo = (sunxi_nand->nand.state == FL_ERASING ? 400 : 20);
+ int ret;
+
+ if (sunxi_nand->selected < 0)
+ return 0;
+
+ rb = &sunxi_nand->sels[sunxi_nand->selected].rb;
+
+ switch (rb->type) {
+ case RB_NATIVE:
+ ret = !!(readl(nfc->regs + NFC_REG_ST) &
+ (NFC_RB_STATE0 << rb->info.nativeid));
+ if (ret)
+ break;
+
+ sunxi_nfc_wait_int(nfc, NFC_RB_B2R, timeo);
+ ret = !!(readl(nfc->regs + NFC_REG_ST) &
+ (NFC_RB_STATE0 << rb->info.nativeid));
+ break;
+ case RB_GPIO:
+ ret = gpio_get_value(rb->info.gpio);
+ break;
+ case RB_NONE:
+ default:
+ ret = 0;
+ dev_err(&mtd->dev, "cannot check R/B NAND status!");
+ break;
+ }
+
+ return ret;
+}
+
+static void sunxi_nfc_select_chip(struct mtd_info *mtd, int chip)
+{
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(mtd);
+ struct nand_chip *nand = &sunxi_nand->nand;
+ struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller);
+ struct sunxi_nand_chip_sel *sel;
+ u32 ctl;
+
+ if (chip > 0 && chip >= sunxi_nand->nsels)
+ return;
+
+ if (chip == sunxi_nand->selected)
+ return;
+
+ ctl = readl(nfc->regs + NFC_REG_CTL) &
+ ~(NFC_CE_SEL | NFC_RB_SEL | NFC_EN);
+
+ if (chip >= 0) {
+ sel = &sunxi_nand->sels[chip];
+
+ ctl |= (sel->cs << 24) | NFC_EN |
+ (((nand->page_shift - 10) & 0xf) << 8);
+ if (sel->rb.type == RB_NONE) {
+ nand->dev_ready = NULL;
+ } else {
+ nand->dev_ready = sunxi_nfc_dev_ready;
+ if (sel->rb.type == RB_NATIVE)
+ ctl |= (sel->rb.info.nativeid << 3);
+ }
+
+ writel(mtd->writesize, nfc->regs + NFC_REG_SPARE_AREA);
+
+ if (nfc->clk_rate != sunxi_nand->clk_rate) {
+ clk_set_rate(nfc->sclk, sunxi_nand->clk_rate);
+ nfc->clk_rate = sunxi_nand->clk_rate;
+ }
+ }
+
+ writel(ctl, nfc->regs + NFC_REG_CTL);
+
+ sunxi_nand->selected = chip;
+}
+
+static void sunxi_nfc_read_buf(struct mtd_info *mtd, uint8_t *buf, int len)
+{
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(mtd);
+ struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller);
+ int cnt;
+ int offs = 0;
+ u32 tmp;
+
+ while (len > offs) {
+ cnt = len - offs;
+ if (cnt > 1024)
+ cnt = 1024;
+
+ while ((readl(nfc->regs + NFC_REG_ST) & NFC_CMD_FIFO_STATUS))
+ ;
+ writel(cnt, nfc->regs + NFC_REG_CNT);
+ tmp = NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD;
+ writel(tmp, nfc->regs + NFC_REG_CMD);
+ sunxi_nfc_wait_int(nfc, NFC_CMD_INT_FLAG, 0);
+ if (buf)
+ memcpy_fromio(buf + offs, nfc->regs + NFC_RAM0_BASE,
+ cnt);
+ offs += cnt;
+ }
+}
+
+static void sunxi_nfc_write_buf(struct mtd_info *mtd, const uint8_t *buf,
+ int len)
+{
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(mtd);
+ struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller);
+ int cnt;
+ int offs = 0;
+ u32 tmp;
+
+ while (len > offs) {
+ cnt = len - offs;
+ if (cnt > 1024)
+ cnt = 1024;
+
+ while ((readl(nfc->regs + NFC_REG_ST) & NFC_CMD_FIFO_STATUS))
+ ;
+ writel(cnt, nfc->regs + NFC_REG_CNT);
+ memcpy_toio(nfc->regs + NFC_RAM0_BASE, buf + offs, cnt);
+ tmp = NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD |
+ NFC_ACCESS_DIR;
+ writel(tmp, nfc->regs + NFC_REG_CMD);
+ sunxi_nfc_wait_int(nfc, NFC_CMD_INT_FLAG, 0);
+ offs += cnt;
+ }
+}
+
+static uint8_t sunxi_nfc_read_byte(struct mtd_info *mtd)
+{
+ uint8_t ret;
+
+ sunxi_nfc_read_buf(mtd, &ret, 1);
+
+ return ret;
+}
+
+static void sunxi_nfc_cmd_ctrl(struct mtd_info *mtd, int dat,
+ unsigned int ctrl)
+{
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(mtd);
+ struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller);
+ u32 tmp;
+
+ while ((readl(nfc->regs + NFC_REG_ST) & NFC_CMD_FIFO_STATUS))
+ ;
+
+ if (ctrl & NAND_CTRL_CHANGE) {
+ tmp = readl(nfc->regs + NFC_REG_CTL);
+ if (ctrl & NAND_NCE)
+ tmp |= NFC_CE_CTL;
+ else
+ tmp &= ~NFC_CE_CTL;
+ writel(tmp, nfc->regs + NFC_REG_CTL);
+ }
+
+ if (dat == NAND_CMD_NONE)
+ return;
+
+ if (ctrl & NAND_CLE) {
+ writel(NFC_SEND_CMD1 | dat, nfc->regs + NFC_REG_CMD);
+ } else {
+ writel(dat, nfc->regs + NFC_REG_ADDR_LOW);
+ writel(NFC_SEND_ADR, nfc->regs + NFC_REG_CMD);
+ }
+
+ sunxi_nfc_wait_int(nfc, NFC_CMD_INT_FLAG, 0);
+}
+
+static int sunxi_nand_chip_init_timings(struct sunxi_nand_chip *chip,
+ struct device_node *np)
+{
+ const struct nand_sdr_timings *timings;
+ u32 min_clk_period = 0;
+ int ret;
+
+ ret = onfi_get_async_timing_mode(&chip->nand);
+ if (ret == ONFI_TIMING_MODE_UNKNOWN) {
+ ret = of_get_nand_onfi_timing_mode(np);
+ if (ret < 0)
+ return ret;
+ }
+
+ ret = fls(ret);
+ if (!ret)
+ return -EINVAL;
+
+ timings = onfi_async_timing_mode_to_sdr_timings(ret - 1);
+ if (IS_ERR(timings))
+ return PTR_ERR(timings);
+
+ /* NFC timings defined in Allwinner Datasheets */
+
+ /* T1 <=> tCLS */
+ if (timings->tCLS_min > min_clk_period)
+ min_clk_period = timings->tCLS_min;
+
+ /* T2 <=> tCLH */
+ if (timings->tCLH_min > min_clk_period)
+ min_clk_period = timings->tCLH_min;
+
+ /* T3 <=> tCS */
+ if (timings->tCS_min > min_clk_period)
+ min_clk_period = timings->tCS_min;
+
+ /* T4 <=> tCH */
+ if (timings->tCH_min > min_clk_period)
+ min_clk_period = timings->tCH_min;
+
+ /* T5 <=> tWP */
+ if (timings->tWP_min > min_clk_period)
+ min_clk_period = timings->tWP_min;
+
+ /* T6 <=> tWH */
+ if (timings->tWH_min > min_clk_period)
+ min_clk_period = timings->tWH_min;
+
+ /* T7 <=> tALS */
+ if (timings->tALS_min > min_clk_period)
+ min_clk_period = timings->tALS_min;
+
+ /* T8 <=> tDS */
+ if (timings->tDS_min > min_clk_period)
+ min_clk_period = timings->tDS_min;
+
+ /* T9 <=> tDH */
+ if (timings->tDH_min > min_clk_period)
+ min_clk_period = timings->tDH_min;
+
+ /* T10 <=> tRR */
+ if (timings->tRR_min > (min_clk_period * 3))
+ min_clk_period = (timings->tRR_min + 2) / 3;
+
+ /* T11 <=> tALH */
+ if (timings->tALH_min > min_clk_period)
+ min_clk_period = timings->tALH_min;
+
+ /* T12 <=> tRP */
+ if (timings->tRP_min > min_clk_period)
+ min_clk_period = timings->tRP_min;
+
+ /* T13 <=> tREH */
+ if (timings->tREH_min > min_clk_period)
+ min_clk_period = timings->tREH_min;
+
+ /* T14 <=> tRC */
+ if (timings->tRC_min > (min_clk_period * 2))
+ min_clk_period = (timings->tRC_min + 1) / 2;
+
+ /* T15 <=> tWC */
+ if (timings->tWC_min > (min_clk_period * 2))
+ min_clk_period = (timings->tWC_min + 1) / 2;
+
+
+ /* min_clk_period = (NAND-clk-period * 2) */
+ if (!min_clk_period) {
+ chip->clk_rate = 20000000;
+ } else {
+ min_clk_period /= 1000;
+ if (!min_clk_period)
+ min_clk_period = 1;
+ chip->clk_rate = (2 * 1000000000) / min_clk_period;
+ }
+
+ /* TODO: configure T16-T19 */
+
+ return 0;
+}
+
+static int sunxi_nand_chip_init(struct device *dev, struct sunxi_nfc *nfc,
+ struct device_node *np)
+{
+ struct sunxi_nand_chip *chip;
+ struct mtd_part_parser_data ppdata;
+ struct mtd_info *mtd;
+ struct nand_chip *nand;
+ u32 strength;
+ u32 blk_size;
+ int nsels;
+ int ret;
+ int i;
+ u32 tmp;
+
+ if (!of_get_property(np, "reg", &nsels))
+ return -EINVAL;
+
+ nsels /= sizeof(u32);
+ if (!nsels)
+ return -EINVAL;
+
+ chip = devm_kzalloc(dev,
+ sizeof(*chip) +
+ (nsels * sizeof(struct sunxi_nand_chip_sel)),
+ GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->nsels = nsels;
+ chip->selected = -1;
+
+ for (i = 0; i < nsels; i++) {
+ ret = of_property_read_u32_index(np, "reg", i, &tmp);
+ if (ret)
+ return ret;
+
+ if (tmp > 7)
+ return -EINVAL;
+
+ if (test_and_set_bit(tmp, &nfc->assigned_cs))
+ return -EINVAL;
+
+ chip->sels[i].cs = tmp;
+
+ if (!of_property_read_u32_index(np, "allwinner,rb", i, &tmp) &&
+ tmp < 2) {
+ chip->sels[i].rb.type = RB_NATIVE;
+ chip->sels[i].rb.info.nativeid = tmp;
+ } else {
+ ret = of_get_named_gpio(np, "rb-gpios", i);
+ if (ret >= 0) {
+ chip->sels[i].rb.type = RB_GPIO;
+ chip->sels[i].rb.info.gpio = tmp;
+ ret = devm_gpio_request(dev, tmp, "nand-rb");
+ if (ret)
+ return ret;
+ } else {
+ chip->sels[i].rb.type = RB_NONE;
+ }
+ }
+ }
+
+ ret = sunxi_nand_chip_init_timings(chip, np);
+ if (ret)
+ return ret;
+
+ nand = &chip->nand;
+ nand->controller = &nfc->controller;
+ nand->select_chip = sunxi_nfc_select_chip;
+ nand->cmd_ctrl = sunxi_nfc_cmd_ctrl;
+ nand->read_buf = sunxi_nfc_read_buf;
+ nand->write_buf = sunxi_nfc_write_buf;
+ nand->read_byte = sunxi_nfc_read_byte;
+
+ nand->ecc.mode = of_get_nand_ecc_mode(np);
+ if (of_get_nand_on_flash_bbt(np))
+ nand->bbt_options |= NAND_BBT_USE_FLASH;
+
+ mtd = &chip->mtd;
+ mtd->priv = nand;
+ mtd->owner = THIS_MODULE;
+
+ ret = nand_scan_ident(mtd, nsels, NULL);
+ if (ret)
+ return ret;
+
+ if (nand->ecc.mode == NAND_ECC_SOFT_BCH) {
+ if (!of_get_nand_ecc_level(np, &strength, &blk_size)) {
+ nand->ecc_step_ds = blk_size;
+ nand->ecc_strength_ds = strength;
+ }
+
+ nand->ecc.size = nand->ecc_step_ds;
+ nand->ecc.bytes = (((nand->ecc_strength_ds *
+ fls(8 * nand->ecc_step_ds)) + 7) / 8);
+ }
+
+ ret = nand_scan_tail(mtd);
+ if (ret)
+ return ret;
+
+ if (of_property_read_string(np, "nand-name", &mtd->name)) {
+ snprintf(chip->default_name, MAX_NAME_SIZE,
+ DEFAULT_NAME_FORMAT, chip->sels[i].cs);
+ mtd->name = chip->default_name;
+ }
+
+ ppdata.of_node = np;
+ ret = mtd_device_parse_register(mtd, NULL, &ppdata, NULL, 0);
+ if (!ret)
+ return ret;
+
+ list_add_tail(&chip->node, &nfc->chips);
+
+ return 0;
+}
+
+static int sunxi_nand_chips_init(struct device *dev, struct sunxi_nfc *nfc)
+{
+ struct device_node *np = dev->of_node;
+ struct device_node *nand_np;
+ int nchips = of_get_child_count(np);
+ int ret;
+
+ if (nchips > 8)
+ return -EINVAL;
+
+ for_each_child_of_node(np, nand_np) {
+ ret = sunxi_nand_chip_init(dev, nfc, nand_np);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int sunxi_nfc_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct resource *r;
+ struct sunxi_nfc *nfc;
+ int ret;
+
+ nfc = devm_kzalloc(dev, sizeof(*nfc), GFP_KERNEL);
+ if (!nfc) {
+ dev_err(dev, "failed to allocate NFC struct\n");
+ return -ENOMEM;
+ }
+
+ spin_lock_init(&nfc->controller.lock);
+ init_waitqueue_head(&nfc->controller.wq);
+ INIT_LIST_HEAD(&nfc->chips);
+
+ r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ nfc->regs = devm_ioremap_resource(dev, r);
+ if (IS_ERR(nfc->regs)) {
+ dev_err(dev, "failed to remap iomem\n");
+ return PTR_ERR(nfc->regs);
+ }
+
+ nfc->irq = platform_get_irq(pdev, 0);
+ if (nfc->irq < 0) {
+ dev_err(dev, "failed to retrieve irq\n");
+ return nfc->irq;
+ }
+
+ nfc->ahb_clk = devm_clk_get(dev, "ahb_clk");
+ if (IS_ERR(nfc->ahb_clk)) {
+ dev_err(dev, "failed to retrieve ahb_clk\n");
+ return PTR_ERR(nfc->ahb_clk);
+ }
+
+ ret = clk_prepare_enable(nfc->ahb_clk);
+ if (ret)
+ return ret;
+
+ nfc->sclk = devm_clk_get(dev, "sclk");
+ if (IS_ERR(nfc->sclk)) {
+ dev_err(dev, "failed to retrieve nand_clk\n");
+ ret = PTR_ERR(nfc->sclk);
+ goto out_ahb_clk_unprepare;
+ }
+
+ ret = clk_prepare_enable(nfc->sclk);
+ if (ret)
+ goto out_ahb_clk_unprepare;
+
+ /* Reset NFC */
+ writel(readl(nfc->regs + NFC_REG_CTL) | NFC_RESET,
+ nfc->regs + NFC_REG_CTL);
+ while (readl(nfc->regs + NFC_REG_CTL) & NFC_RESET)
+ ;
+
+ writel(0, nfc->regs + NFC_REG_INT);
+ ret = devm_request_irq(dev, nfc->irq, sunxi_nfc_interrupt,
+ 0, "sunxi-nand", nfc);
+ if (ret)
+ goto out_sclk_unprepare;
+
+ platform_set_drvdata(pdev, nfc);
+
+ writel(0x100, nfc->regs + NFC_REG_TIMING_CTL);
+ writel(0x7ff, nfc->regs + NFC_REG_TIMING_CFG);
+
+ ret = sunxi_nand_chips_init(dev, nfc);
+ if (ret) {
+ dev_err(dev, "failed to init nand chips\n");
+ goto out_sclk_unprepare;
+ }
+
+ return 0;
+
+out_sclk_unprepare:
+ clk_disable_unprepare(nfc->sclk);
+out_ahb_clk_unprepare:
+ clk_disable_unprepare(nfc->ahb_clk);
+
+ return ret;
+}
+
+static const struct of_device_id sunxi_nfc_ids[] = {
+ { .compatible = "allwinner,sun4i-nand" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, sunxi_nfc_ids);
+
+static struct platform_driver sunxi_nfc_driver = {
+ .driver = {
+ .name = "sunxi_nand",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(sunxi_nfc_ids),
+ },
+ .probe = sunxi_nfc_probe,
+};
+module_platform_driver(sunxi_nfc_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Boris BREZILLON");
+MODULE_DESCRIPTION("Allwinner NAND Flash Controller driver");
+MODULE_ALIAS("platform:sunxi_nfc");
--
1.7.9.5
Add a converter to retrieve NAND timings from an ONFI NAND timing mode.
This only support SDR NAND timings for now.
Signed-off-by: Boris BREZILLON <[email protected]>
---
drivers/mtd/nand/Makefile | 2 +-
drivers/mtd/nand/nand_timings.c | 248 +++++++++++++++++++++++++++++++++++++++
include/linux/mtd/nand.h | 4 +
3 files changed, 253 insertions(+), 1 deletion(-)
create mode 100644 drivers/mtd/nand/nand_timings.c
diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile
index 542b568..bbea7a6 100644
--- a/drivers/mtd/nand/Makefile
+++ b/drivers/mtd/nand/Makefile
@@ -2,7 +2,7 @@
# linux/drivers/nand/Makefile
#
-obj-$(CONFIG_MTD_NAND) += nand.o
+obj-$(CONFIG_MTD_NAND) += nand.o nand_timings.o
obj-$(CONFIG_MTD_NAND_ECC) += nand_ecc.o
obj-$(CONFIG_MTD_NAND_BCH) += nand_bch.o
obj-$(CONFIG_MTD_NAND_IDS) += nand_ids.o
diff --git a/drivers/mtd/nand/nand_timings.c b/drivers/mtd/nand/nand_timings.c
new file mode 100644
index 0000000..f66fe95
--- /dev/null
+++ b/drivers/mtd/nand/nand_timings.c
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2014 Boris BREZILLON <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+#include <linux/mtd/nand.h>
+
+static const struct nand_sdr_timings onfi_sdr_timings[] = {
+ /* Mode 0 */
+ {
+ .tADL_min = 200000,
+ .tALH_min = 20000,
+ .tALS_min = 50000,
+ .tAR_min = 25000,
+ .tCEA_max = 100000,
+ .tCEH_min = 20000,
+ .tCH_min = 20000,
+ .tCHZ_max = 100000,
+ .tCLH_min = 20000,
+ .tCLR_min = 20000,
+ .tCLS_min = 50000,
+ .tCOH_min = 0,
+ .tCS_min = 70000,
+ .tDH_min = 20000,
+ .tDS_min = 40000,
+ .tFEAT_max = 1000000,
+ .tIR_min = 10000,
+ .tITC_max = 1000000,
+ .tRC_min = 100000,
+ .tREA_max = 40000,
+ .tREH_min = 30000,
+ .tRHOH_min = 0,
+ .tRHW_min = 200000,
+ .tRHZ_max = 200000,
+ .tRLOH_min = 0,
+ .tRP_min = 50000,
+ .tRST_max = 250000000000,
+ .tWB_max = 200000,
+ .tRR_min = 40000,
+ .tWC_min = 100000,
+ .tWH_min = 30000,
+ .tWHR_min = 120000,
+ .tWP_min = 50000,
+ .tWW_min = 100000,
+ },
+ /* Mode 1 */
+ {
+ .tADL_min = 100000,
+ .tALH_min = 10000,
+ .tALS_min = 25000,
+ .tAR_min = 10000,
+ .tCEA_max = 45000,
+ .tCEH_min = 20000,
+ .tCH_min = 10000,
+ .tCHZ_max = 50000,
+ .tCLH_min = 10000,
+ .tCLR_min = 10000,
+ .tCLS_min = 25000,
+ .tCOH_min = 15000,
+ .tCS_min = 35000,
+ .tDH_min = 10000,
+ .tDS_min = 20000,
+ .tFEAT_max = 1000000,
+ .tIR_min = 0,
+ .tITC_max = 1000000,
+ .tRC_min = 50000,
+ .tREA_max = 30000,
+ .tREH_min = 15000,
+ .tRHOH_min = 15000,
+ .tRHW_min = 100000,
+ .tRHZ_max = 100000,
+ .tRLOH_min = 0,
+ .tRP_min = 25000,
+ .tRR_min = 20000,
+ .tRST_max = 500000000,
+ .tWB_max = 100000,
+ .tWC_min = 45000,
+ .tWH_min = 15000,
+ .tWHR_min = 80000,
+ .tWP_min = 25000,
+ .tWW_min = 100000,
+ },
+ /* Mode 2 */
+ {
+ .tADL_min = 100000,
+ .tALH_min = 10000,
+ .tALS_min = 15000,
+ .tAR_min = 10000,
+ .tCEA_max = 30000,
+ .tCEH_min = 20000,
+ .tCH_min = 10000,
+ .tCHZ_max = 50000,
+ .tCLH_min = 10000,
+ .tCLR_min = 10000,
+ .tCLS_min = 15000,
+ .tCOH_min = 15000,
+ .tCS_min = 25000,
+ .tDH_min = 5000,
+ .tDS_min = 15000,
+ .tFEAT_max = 1000000,
+ .tIR_min = 0,
+ .tITC_max = 1000000,
+ .tRC_min = 35000,
+ .tREA_max = 25000,
+ .tREH_min = 15000,
+ .tRHOH_min = 15000,
+ .tRHW_min = 100000,
+ .tRHZ_max = 100000,
+ .tRLOH_min = 0,
+ .tRR_min = 20000,
+ .tRST_max = 500000000,
+ .tWB_max = 100000,
+ .tRP_min = 17000,
+ .tWC_min = 35000,
+ .tWH_min = 15000,
+ .tWHR_min = 80000,
+ .tWP_min = 17000,
+ .tWW_min = 100000,
+ },
+ /* Mode 3 */
+ {
+ .tADL_min = 100000,
+ .tALH_min = 5000,
+ .tALS_min = 10000,
+ .tAR_min = 10000,
+ .tCEA_max = 25000,
+ .tCEH_min = 20000,
+ .tCH_min = 5000,
+ .tCHZ_max = 50000,
+ .tCLH_min = 5000,
+ .tCLR_min = 10000,
+ .tCLS_min = 10000,
+ .tCOH_min = 15000,
+ .tCS_min = 25000,
+ .tDH_min = 5000,
+ .tDS_min = 10000,
+ .tFEAT_max = 1000000,
+ .tIR_min = 0,
+ .tITC_max = 1000000,
+ .tRC_min = 30000,
+ .tREA_max = 20000,
+ .tREH_min = 10000,
+ .tRHOH_min = 15000,
+ .tRHW_min = 100000,
+ .tRHZ_max = 100000,
+ .tRLOH_min = 0,
+ .tRP_min = 15000,
+ .tRR_min = 20000,
+ .tRST_max = 500000000,
+ .tWB_max = 100000,
+ .tWC_min = 30000,
+ .tWH_min = 10000,
+ .tWHR_min = 80000,
+ .tWP_min = 15000,
+ .tWW_min = 100000,
+ },
+ /* Mode 4 */
+ {
+ .tADL_min = 70000,
+ .tALH_min = 5000,
+ .tALS_min = 10000,
+ .tAR_min = 10000,
+ .tCEA_max = 25000,
+ .tCEH_min = 20000,
+ .tCH_min = 5000,
+ .tCHZ_max = 30000,
+ .tCLH_min = 5000,
+ .tCLR_min = 10000,
+ .tCLS_min = 10000,
+ .tCOH_min = 15000,
+ .tCS_min = 20000,
+ .tDH_min = 5000,
+ .tDS_min = 10000,
+ .tFEAT_max = 1000000,
+ .tIR_min = 0,
+ .tITC_max = 1000000,
+ .tRC_min = 25000,
+ .tREA_max = 20000,
+ .tREH_min = 10000,
+ .tRHOH_min = 15000,
+ .tRHW_min = 100000,
+ .tRHZ_max = 100000,
+ .tRLOH_min = 5000,
+ .tRP_min = 12000,
+ .tRR_min = 20000,
+ .tRST_max = 500000000,
+ .tWB_max = 100000,
+ .tWC_min = 25000,
+ .tWH_min = 10000,
+ .tWHR_min = 80000,
+ .tWP_min = 12000,
+ .tWW_min = 100000,
+ },
+ /* Mode 5 */
+ {
+ .tADL_min = 70000,
+ .tALH_min = 5000,
+ .tALS_min = 10000,
+ .tAR_min = 10000,
+ .tCEA_max = 25000,
+ .tCEH_min = 20000,
+ .tCH_min = 5000,
+ .tCHZ_max = 30000,
+ .tCLH_min = 5000,
+ .tCLR_min = 10000,
+ .tCLS_min = 10000,
+ .tCOH_min = 15000,
+ .tCS_min = 15000,
+ .tDH_min = 5000,
+ .tDS_min = 7000,
+ .tFEAT_max = 1000000,
+ .tIR_min = 0,
+ .tITC_max = 1000000,
+ .tRC_min = 20000,
+ .tREA_max = 16000,
+ .tREH_min = 7000,
+ .tRHOH_min = 15000,
+ .tRHW_min = 100000,
+ .tRHZ_max = 100000,
+ .tRLOH_min = 5000,
+ .tRP_min = 10000,
+ .tRR_min = 20000,
+ .tRST_max = 500000000,
+ .tWB_max = 100000,
+ .tWC_min = 20000,
+ .tWH_min = 7000,
+ .tWHR_min = 80000,
+ .tWP_min = 10000,
+ .tWW_min = 100000,
+ },
+};
+
+/**
+ * onfi_async_timing_mode_to_sdr_timings - [NAND Interface] Retrieve NAND
+ * timings according to the given ONFI timing mode
+ * @mode: ONFI timing mode
+ */
+const struct nand_sdr_timings *onfi_async_timing_mode_to_sdr_timings(int mode)
+{
+ if (mode < 0 || mode > ARRAY_SIZE(onfi_sdr_timings))
+ return ERR_PTR(-EINVAL);
+
+ return &onfi_sdr_timings[mode];
+}
+EXPORT_SYMBOL(onfi_async_timing_mode_to_sdr_timings);
diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h
index 67f0829..c70e0a3 100644
--- a/include/linux/mtd/nand.h
+++ b/include/linux/mtd/nand.h
@@ -806,6 +806,7 @@ static inline bool nand_is_slc(struct nand_chip *chip)
return chip->bits_per_cell == 1;
}
+
/**
* struct nand_sdr_timings - SDR NAND chip timings
*
@@ -854,4 +855,7 @@ struct nand_sdr_timings {
u32 tWW_min;
};
+/* convert an ONFI timing mode to its timing characteristics. */
+const struct nand_sdr_timings *onfi_async_timing_mode_to_sdr_timings(int mode);
+
#endif /* __LINUX_MTD_NAND_H */
--
1.7.9.5
The Hynix nand flashes store their ECC requirements in byte 4 of its id
(returned on READ ID command).
Signed-off-by: Boris BREZILLON <[email protected]>
---
drivers/mtd/nand/nand_base.c | 37 +++++++++++++++++++++++++++++++++++++
1 file changed, 37 insertions(+)
diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c
index bd39f7b..15069ec 100644
--- a/drivers/mtd/nand/nand_base.c
+++ b/drivers/mtd/nand/nand_base.c
@@ -3202,6 +3202,43 @@ static void nand_decode_ext_id(struct mtd_info *mtd, struct nand_chip *chip,
else
mtd->erasesize = (64 * 1024) << tmp;
*busw = 0;
+
+ /* Retrieve ECC infos */
+ switch ((id_data[4] >> 4) & 0x7) {
+ case 1:
+ chip->ecc_step_ds = 512;
+ chip->ecc_strength_ds = 1;
+ break;
+ case 2:
+ chip->ecc_step_ds = 512;
+ chip->ecc_strength_ds = 2;
+ break;
+ case 3:
+ chip->ecc_step_ds = 512;
+ chip->ecc_strength_ds = 4;
+ break;
+ case 4:
+ chip->ecc_step_ds = 512;
+ chip->ecc_strength_ds = 8;
+ break;
+ case 5:
+ chip->ecc_step_ds = 1024;
+ chip->ecc_strength_ds = 24;
+ break;
+ case 6:
+ chip->ecc_step_ds = 1024;
+ chip->ecc_strength_ds = 32;
+ break;
+ case 7:
+ chip->ecc_step_ds = 1024;
+ chip->ecc_strength_ds = 40;
+ break;
+ case 0:
+ default:
+ chip->ecc_step_ds = 0;
+ chip->ecc_strength_ds = 0;
+ break;
+ }
} else {
/* Calc pagesize */
mtd->writesize = 1024 << (extid & 0x03);
--
1.7.9.5
Add documentation for the ONFI NAND timing mode property.
Signed-off-by: Boris BREZILLON <[email protected]>
---
Documentation/devicetree/bindings/mtd/nand.txt | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/Documentation/devicetree/bindings/mtd/nand.txt b/Documentation/devicetree/bindings/mtd/nand.txt
index 0c962296..75e46f3 100644
--- a/Documentation/devicetree/bindings/mtd/nand.txt
+++ b/Documentation/devicetree/bindings/mtd/nand.txt
@@ -8,3 +8,8 @@
E.g. : nand-ecc-level = <4 512>; /* 4 bits / 512 bytes */
- nand-bus-width : 8 or 16 bus width if not present 8
- nand-on-flash-bbt: boolean to enable on flash bbt option if not present false
+- onfi,nand-timing-mode: an integer encoding the ONFI timing mode of the NAND
+ chip. This is only used when the chip does not support the ONFI standard.
+ Choose the closest mode fulfilling the NAND chip timings.
+ For a full description of the different timing modes see this document:
+ http://www.onfi.org/~/media/ONFI/specs/onfi_3_1_spec.pdf
--
1.7.9.5
nand-ecc-level property statically defines NAND chip's ECC requirements.
Signed-off-by: Boris BREZILLON <[email protected]>
---
Documentation/devicetree/bindings/mtd/nand.txt | 3 +++
1 file changed, 3 insertions(+)
diff --git a/Documentation/devicetree/bindings/mtd/nand.txt b/Documentation/devicetree/bindings/mtd/nand.txt
index 03855c8..0c962296 100644
--- a/Documentation/devicetree/bindings/mtd/nand.txt
+++ b/Documentation/devicetree/bindings/mtd/nand.txt
@@ -3,5 +3,8 @@
- nand-ecc-mode : String, operation mode of the NAND ecc mode.
Supported values are: "none", "soft", "hw", "hw_syndrome", "hw_oob_first",
"soft_bch".
+- nand-ecc-level : Two cells property defining the ECC level requirements.
+ The first cell represent the strength and the second cell the ECC block size.
+ E.g. : nand-ecc-level = <4 512>; /* 4 bits / 512 bytes */
- nand-bus-width : 8 or 16 bus width if not present 8
- nand-on-flash-bbt: boolean to enable on flash bbt option if not present false
--
1.7.9.5
On Wed, Jan 29, 2014 at 8:34 AM, Boris BREZILLON
<[email protected]> wrote:
> Add the sunxi NAND Flash Controller dt bindings documentation.
>
> Signed-off-by: Boris BREZILLON <[email protected]>
> ---
> .../devicetree/bindings/mtd/sunxi-nand.txt | 46 ++++++++++++++++++++
> 1 file changed, 46 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/mtd/sunxi-nand.txt
>
> diff --git a/Documentation/devicetree/bindings/mtd/sunxi-nand.txt b/Documentation/devicetree/bindings/mtd/sunxi-nand.txt
> new file mode 100644
> index 0000000..b0e55a3
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/mtd/sunxi-nand.txt
> @@ -0,0 +1,46 @@
> +Allwinner NAND Flash Controller (NFC)
> +
> +Required properties:
> +- compatible : "allwinner,sun4i-nand".
> +- reg : shall contain registers location and length for data and reg.
> +- interrupts : shall define the nand controller interrupt.
> +- #address-cells: shall be set to 1. Encode the nand CS.
> +- #size-cells : shall be set to 0.
> +- clocks : shall reference nand controller clocks.
> +- clock-names : nand controller internal clock names. Shall contain :
> + * "ahb_clk" : AHB gating clock
> + * "sclk" : nand controller clock
> +
> +Optional children nodes:
> +Children nodes represent the available nand chips.
> +
> +Optional properties:
For the controller or per nand chip?
> +- onfi,nand-timing-mode : mandatory if the chip does not support the ONFI
> + standard.
Add to generic nand binding.
> +- allwinner,rb : shall contain the native Ready/Busy ids.
> + or
> +- rb-gpios : shall contain the gpios used as R/B pins.
Isn't allwinner,rb implied by a lack of rb-gpios property. Or no R/B
pin is an option? If so, don't you need some fixed time delay
properties like max erase time?
rb-gpios could be added to the generic nand binding as well.
Rob
On Wed, Jan 29, 2014 at 11:11 AM, Rob Herring <[email protected]> wrote:
> On Wed, Jan 29, 2014 at 8:34 AM, Boris BREZILLON
> <[email protected]> wrote:
>> Add the sunxi NAND Flash Controller dt bindings documentation.
>>
[snip]
>> +- onfi,nand-timing-mode : mandatory if the chip does not support the ONFI
>> + standard.
>
> Add to generic nand binding.
NM, I see you did this.
Rob
On Wed, Jan 29, 2014 at 03:34:13PM +0100, Boris BREZILLON wrote:
> nand-ecc-level property statically defines NAND chip's ECC requirements.
>
> Signed-off-by: Boris BREZILLON <[email protected]>
> ---
> Documentation/devicetree/bindings/mtd/nand.txt | 3 +++
> 1 file changed, 3 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/mtd/nand.txt b/Documentation/devicetree/bindings/mtd/nand.txt
> index 03855c8..0c962296 100644
> --- a/Documentation/devicetree/bindings/mtd/nand.txt
> +++ b/Documentation/devicetree/bindings/mtd/nand.txt
> @@ -3,5 +3,8 @@
> - nand-ecc-mode : String, operation mode of the NAND ecc mode.
> Supported values are: "none", "soft", "hw", "hw_syndrome", "hw_oob_first",
> "soft_bch".
> +- nand-ecc-level : Two cells property defining the ECC level requirements.
> + The first cell represent the strength and the second cell the ECC block size.
> + E.g. : nand-ecc-level = <4 512>; /* 4 bits / 512 bytes */
> - nand-bus-width : 8 or 16 bus width if not present 8
> - nand-on-flash-bbt: boolean to enable on flash bbt option if not present false
Hm.. when was this proposal agreed? It seems I've missed the
discussion...
FWIW, we've already proposed an equivalent one, but it received no
feedback from the devicetree maintainers:
http://comments.gmane.org/gmane.linux.drivers.devicetree/58764
Maybe we can discuss about it now?
nand-ecc-strength : integer ECC required strength.
nand-ecc-size : integer step size associated to the ECC strength.
vs.
nand-ecc-level : Two cells property defining the ECC level requirements.
The first cell represent the strength and the second cell the ECC block size.
E.g. : nand-ecc-level = <4 512>; /* 4 bits / 512 bytes */
It's really the same proposal but with a different format, right?
IMHO, the former is more human-readable, but other than that I see no
difference.
Brian? DT-guys?
--
Ezequiel García, Free Electrons
Embedded Linux, Kernel and Android Engineering
http://free-electrons.com
On Wed, Jan 29, 2014 at 03:34:18PM +0100, Boris BREZILLON wrote:
> +static int sunxi_nand_chip_init_timings(struct sunxi_nand_chip *chip,
> + struct device_node *np)
> +{
> + const struct nand_sdr_timings *timings;
> + u32 min_clk_period = 0;
> + int ret;
> +
> + ret = onfi_get_async_timing_mode(&chip->nand);
> + if (ret == ONFI_TIMING_MODE_UNKNOWN) {
> + ret = of_get_nand_onfi_timing_mode(np);
> + if (ret < 0)
> + return ret;
> + }
[..]
> +static int sunxi_nand_chip_init(struct device *dev, struct sunxi_nfc *nfc,
[..]
> + ret = sunxi_nand_chip_init_timings(chip, np);
> + if (ret)
> + return ret;
[..]
> + ret = nand_scan_ident(mtd, nsels, NULL);
This ordering looks a bit problematic, will onfi_get_async_timing_mode
ever return anything other than ONFI_TIMING_MODE_UNKNOWN if it is
called before nand_scan_ident ? What sets clk_rate to non-zero if there
is no DT property?
For a flow that uses onfi_get_async_timing_mode rather than DT the
driver should set the interface to timing mode 0 (slowest) and then
call nand_scan_ident, and then reset the interface to the detected
timing mode.
Maybe this should be implemented in the core code through a new
callback (nand->set_timing_mode ?)
Regards,
Jason
Hello Rob,
Le 29/01/2014 18:11, Rob Herring a ?crit :
> On Wed, Jan 29, 2014 at 8:34 AM, Boris BREZILLON
> <[email protected]> wrote:
>> Add the sunxi NAND Flash Controller dt bindings documentation.
>>
>> Signed-off-by: Boris BREZILLON <[email protected]>
>> ---
>> .../devicetree/bindings/mtd/sunxi-nand.txt | 46 ++++++++++++++++++++
>> 1 file changed, 46 insertions(+)
>> create mode 100644 Documentation/devicetree/bindings/mtd/sunxi-nand.txt
>>
>> diff --git a/Documentation/devicetree/bindings/mtd/sunxi-nand.txt b/Documentation/devicetree/bindings/mtd/sunxi-nand.txt
>> new file mode 100644
>> index 0000000..b0e55a3
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/mtd/sunxi-nand.txt
>> @@ -0,0 +1,46 @@
>> +Allwinner NAND Flash Controller (NFC)
>> +
>> +Required properties:
>> +- compatible : "allwinner,sun4i-nand".
>> +- reg : shall contain registers location and length for data and reg.
>> +- interrupts : shall define the nand controller interrupt.
>> +- #address-cells: shall be set to 1. Encode the nand CS.
>> +- #size-cells : shall be set to 0.
>> +- clocks : shall reference nand controller clocks.
>> +- clock-names : nand controller internal clock names. Shall contain :
>> + * "ahb_clk" : AHB gating clock
>> + * "sclk" : nand controller clock
>> +
>> +Optional children nodes:
>> +Children nodes represent the available nand chips.
>> +
>> +Optional properties:
> For the controller or per nand chip?
>
>> +- onfi,nand-timing-mode : mandatory if the chip does not support the ONFI
>> + standard.
> Add to generic nand binding.
>
>> +- allwinner,rb : shall contain the native Ready/Busy ids.
>> + or
>> +- rb-gpios : shall contain the gpios used as R/B pins.
> Isn't allwinner,rb implied by a lack of rb-gpios property. Or no R/B
> pin is an option?
Both are optional. In case none of the properties are defined the dev_ready
callback is set to NULL and the nand_base waiting loop is used.
> If so, don't you need some fixed time delay
> properties like max erase time?
This is handled in nand_base (using the chip_delay field), but I guess
we could
use the information retrieved from nand timings and the operation in
progress...
> rb-gpios could be added to the generic nand binding as well.
Sure.
>
> Rob
Dear Rob, and other DT maintainers,
>From: Rob Herring
[...]
>> +- onfi,nand-timing-mode : mandatory if the chip does not support the ONFI
>> + standard.
>
>Add to generic nand binding.
>
>> +- allwinner,rb : shall contain the native Ready/Busy ids.
>> + or
>> +- rb-gpios : shall contain the gpios used as R/B pins.
>
>Isn't allwinner,rb implied by a lack of rb-gpios property. Or no R/B
>pin is an option? If so, don't you need some fixed time delay
>properties like max erase time?
>
>rb-gpios could be added to the generic nand binding as well.
>
I do think this should go into generic nand binding, as this is controller specific.
Some controllers have dedicated R/B pin (Ready/Busy) while others may use
GPIO instead. It's the way a hardware controller is designed.
Request you to please consider Ack from MTD Maintainers 'at-least' for
generic NAND DT bindings. There is already a discussion going in
a separate thread for which is still not awaiting replies [1].
[1] http://lists.infradead.org/pipermail/linux-mtd/2014-January/051625.html
with regards, pekon
Le 29/01/2014 19:02, Gupta, Pekon a ?crit :
> Dear Rob, and other DT maintainers,
>
>> From: Rob Herring
> [...]
>>> +- onfi,nand-timing-mode : mandatory if the chip does not support the ONFI
>>> + standard.
>> Add to generic nand binding.
>>
>>> +- allwinner,rb : shall contain the native Ready/Busy ids.
>>> + or
>>> +- rb-gpios : shall contain the gpios used as R/B pins.
>> Isn't allwinner,rb implied by a lack of rb-gpios property. Or no R/B
>> pin is an option? If so, don't you need some fixed time delay
>> properties like max erase time?
>>
>> rb-gpios could be added to the generic nand binding as well.
>>
> I do think this should go into generic nand binding, as this is controller specific.
> Some controllers have dedicated R/B pin (Ready/Busy) while others may use
> GPIO instead. It's the way a hardware controller is designed.
You meant "You do not think", right ?
If so, I think even if the retrieval and control of the GPIO is done is
each NAND
controller, we could at least use a common property name for all drivers
using
a GPIO to detect the R/B state.
> Request you to please consider Ack from MTD Maintainers 'at-least' for
> generic NAND DT bindings. There is already a discussion going in
> a separate thread for which is still not awaiting replies [1].
>
> [1]http://lists.infradead.org/pipermail/linux-mtd/2014-January/051625.html
I missed this thread, but I can definitely use the nand-ecc-strength and
nand-ecc-step-size instead of the one I defined (nand-ecc-level), as long
as there is a proper way to define these informations in the DT.
I'll let DT and MTD maintainers decide ;-).
Best Regards,
Boris
>
> with regards, pekon
Dear Rob, and other DT maintainers,
(apologies, fixed typos in earlier mail)
>>From: Rob Herring
>[...]
>>> +- onfi,nand-timing-mode : mandatory if the chip does not support the ONFI
>>> + standard.
>>
>>Add to generic nand binding.
>>
>>> +- allwinner,rb : shall contain the native Ready/Busy ids.
>>> + or
>>> +- rb-gpios : shall contain the gpios used as R/B pins.
>>
>>Isn't allwinner,rb implied by a lack of rb-gpios property. Or no R/B
>>pin is an option? If so, don't you need some fixed time delay
>>properties like max erase time?
>>
>>rb-gpios could be added to the generic nand binding as well.
>>
I do _not_ think this should go into generic nand binding, as this is controller specific.
Some controllers have dedicated R/B pin (Ready/Busy) while others may use
GPIO instead. It's the way a hardware controller is designed.
Request you to please consider Ack from MTD Maintainers 'at-least' for
generic NAND DT bindings. There is already a discussion going in
a separate thread for which there are still no replies [1].
[1] http://lists.infradead.org/pipermail/linux-mtd/2014-January/051625.html
with regards, pekon
Hello Ezequiel
Le 29/01/2014 18:53, Ezequiel Garcia a écrit :
> On Wed, Jan 29, 2014 at 03:34:13PM +0100, Boris BREZILLON wrote:
>> nand-ecc-level property statically defines NAND chip's ECC requirements.
>>
>> Signed-off-by: Boris BREZILLON <[email protected]>
>> ---
>> Documentation/devicetree/bindings/mtd/nand.txt | 3 +++
>> 1 file changed, 3 insertions(+)
>>
>> diff --git a/Documentation/devicetree/bindings/mtd/nand.txt b/Documentation/devicetree/bindings/mtd/nand.txt
>> index 03855c8..0c962296 100644
>> --- a/Documentation/devicetree/bindings/mtd/nand.txt
>> +++ b/Documentation/devicetree/bindings/mtd/nand.txt
>> @@ -3,5 +3,8 @@
>> - nand-ecc-mode : String, operation mode of the NAND ecc mode.
>> Supported values are: "none", "soft", "hw", "hw_syndrome", "hw_oob_first",
>> "soft_bch".
>> +- nand-ecc-level : Two cells property defining the ECC level requirements.
>> + The first cell represent the strength and the second cell the ECC block size.
>> + E.g. : nand-ecc-level = <4 512>; /* 4 bits / 512 bytes */
>> - nand-bus-width : 8 or 16 bus width if not present 8
>> - nand-on-flash-bbt: boolean to enable on flash bbt option if not present false
> Hm.. when was this proposal agreed?
Never, this is a proposal based on my needs, and this was not present in the
1st version of this series :-).
> It seems I've missed the
> discussion...
>
> FWIW, we've already proposed an equivalent one, but it received no
> feedback from the devicetree maintainers:
>
> http://comments.gmane.org/gmane.linux.drivers.devicetree/58764
>
> Maybe we can discuss about it now?
>
> nand-ecc-strength : integer ECC required strength.
> nand-ecc-size : integer step size associated to the ECC strength.
>
> vs.
>
> nand-ecc-level : Two cells property defining the ECC level requirements.
> The first cell represent the strength and the second cell the ECC block size.
> E.g. : nand-ecc-level = <4 512>; /* 4 bits / 512 bytes */
>
> It's really the same proposal but with a different format, right?
Yes it is.
> IMHO, the former is more human-readable, but other than that I see no
> difference.
As I already said to Pekon, I won't complain if my proposal is not chosen,
as long as there is a proper way to define these ECC requirements ;-).
Best Regards,
Boris
>
> Brian? DT-guys?
On Wed, Jan 29, 2014 at 10:56:42AM -0700, Jason Gunthorpe wrote:
> On Wed, Jan 29, 2014 at 03:34:18PM +0100, Boris BREZILLON wrote:
>
> [..]
>
> > +static int sunxi_nand_chip_init(struct device *dev, struct sunxi_nfc *nfc,
> [..]
> > + ret = sunxi_nand_chip_init_timings(chip, np);
> > + if (ret)
> > + return ret;
> [..]
> > + ret = nand_scan_ident(mtd, nsels, NULL);
>
> This ordering looks a bit problematic, will onfi_get_async_timing_mode
> ever return anything other than ONFI_TIMING_MODE_UNKNOWN if it is
> called before nand_scan_ident ? What sets clk_rate to non-zero if there
> is no DT property?
>
> For a flow that uses onfi_get_async_timing_mode rather than DT the
> driver should set the interface to timing mode 0 (slowest) and then
> call nand_scan_ident, and then reset the interface to the detected
> timing mode.
>
Yes. And I believe this is a requirement from the ONFI 2.1 spec:
"""
4.1.4.3. Source Synchronous to Asynchronous
[..]
The host shall transition to the asynchronous data interface. Then the
host shall issue the Reset (FFh) command described in the previous paragraph
using asynchronous timing mode 0, thus the host transitions to the asynchronous
data interface prior to issuing the Reset (FFh). A device in any timing mode is
required to recognize a Reset (FFh) command issued in asynchronous timing
mode 0.
[..]
After CE# has been pulled high and then transitioned low again, the host
should issue a Set Features to select the appropriate asynchronous timing mode.
"""
--
Ezequiel García, Free Electrons
Embedded Linux, Kernel and Android Engineering
http://free-electrons.com
Le 29/01/2014 18:56, Jason Gunthorpe a ?crit :
> On Wed, Jan 29, 2014 at 03:34:18PM +0100, Boris BREZILLON wrote:
>
>> +static int sunxi_nand_chip_init_timings(struct sunxi_nand_chip *chip,
>> + struct device_node *np)
>> +{
>> + const struct nand_sdr_timings *timings;
>> + u32 min_clk_period = 0;
>> + int ret;
>> +
>> + ret = onfi_get_async_timing_mode(&chip->nand);
>> + if (ret == ONFI_TIMING_MODE_UNKNOWN) {
>> + ret = of_get_nand_onfi_timing_mode(np);
>> + if (ret < 0)
>> + return ret;
>> + }
> [..]
>
>> +static int sunxi_nand_chip_init(struct device *dev, struct sunxi_nfc *nfc,
> [..]
>> + ret = sunxi_nand_chip_init_timings(chip, np);
>> + if (ret)
>> + return ret;
> [..]
>> + ret = nand_scan_ident(mtd, nsels, NULL);
> This ordering looks a bit problematic, will onfi_get_async_timing_mode
> ever return anything other than ONFI_TIMING_MODE_UNKNOWN if it is
> called before nand_scan_ident ?
Indeed. I haven't tested this part as I don't own any board with an ONFI
compatible chip.
> What sets clk_rate to non-zero if there
> is no DT property?
It is set to 20 MHz by default, but it should definitely be set to the
rate fulfilling mode 0.
I'll fix this.
>
> For a flow that uses onfi_get_async_timing_mode rather than DT the
> driver should set the interface to timing mode 0 (slowest) and then
> call nand_scan_ident, and then reset the interface to the detected
> timing mode.
Absolutely.
>
> Maybe this should be implemented in the core code through a new
> callback (nand->set_timing_mode ?)
>
> Regards,
> Jason
On Wed, Jan 29, 2014 at 03:46:20PM -0300, Ezequiel Garcia wrote:
> After CE# has been pulled high and then transitioned low again, the host
> should issue a Set Features to select the appropriate asynchronous timing mode.
> """
Oh, I had forgot you should do a set feature too
Boris, I think the core core should handle this dance and the driver
should just implement a call back to change the timing mode on the
interface..
If I ever get a moment I can work on support for timing setting in the
mvebu driver, I have boards here with ONFI NAND..
Regards,
Jason
ons 2014-01-29 klockan 11:11 -0600 skrev Rob Herring:
> Isn't allwinner,rb implied by a lack of rb-gpios property. Or no R/B
> pin is an option? If so, don't you need some fixed time delay
> properties like max erase time?
>
> rb-gpios could be added to the generic nand binding as well.
The Allwinner NAND controller have dedicated RB pins when NAND is
enabled, only MUXed with other functions when NAND is not enabled.
Leaving RB unconnected is not a valid hardware configuration. The
controller internal timing engine depends on being able to sense RB to
sequence NAND commands properly.
Regards
Henrik
Hello Henrik,
On 29/01/2014 23:37, Henrik Nordström wrote:
> ons 2014-01-29 klockan 11:11 -0600 skrev Rob Herring:
>
>> Isn't allwinner,rb implied by a lack of rb-gpios property. Or no R/B
>> pin is an option? If so, don't you need some fixed time delay
>> properties like max erase time?
>>
>> rb-gpios could be added to the generic nand binding as well.
> The Allwinner NAND controller have dedicated RB pins when NAND is
> enabled, only MUXed with other functions when NAND is not enabled.
>
> Leaving RB unconnected is not a valid hardware configuration. The
> controller internal timing engine depends on being able to sense RB to
> sequence NAND commands properly.
This is not true (at least in this driver). It was in yuq's driver because
he was using the NFC_WAIT_FLAG ,and in this case the controller wait
for the native R/B pin to be high before considering the CMD is complete.
This driver choose the appropriate way to test the R/B state of the
NAND chip according to what was specified in the DT:
- allwinner,rb: native R/B id. These pins will be used by the NAND
controller to test the R/B state. Only 0 and 1 are valid because the
NAND controller only support 2 R/B pins.
- rb-gpios: gpio used for R/B tests. This is a simple GPIO and will
use the GPIO subsystem to test the R/B pin state.
- none: the NAND base code will wait some time before and send
STATUS cmd to the NAND to check its status.
BTW, the controller supports 8 CS (8 NAND chips), but only have 2 native
R/B pins, this means you'll have to use the GPIO or standard GET_STATUS
method if you connect 3 or more NAND chips.
And for the record, I still think the rb-gpios property (or whatever
common name you choose: nand-rb-gpios ?) should be part of
the generic NAND binding, because other controllers (at least the
atmel one :)) use GPIOs to test R/B state.
Best Regards,
Boris
>
> Regards
> Henrik
>
Hello Henrik,
Sorry for the noise, I sent the mail to Rob's old address.
On 29/01/2014 23:37, Henrik Nordström wrote:
> ons 2014-01-29 klockan 11:11 -0600 skrev Rob Herring:
>
>> Isn't allwinner,rb implied by a lack of rb-gpios property. Or no R/B
>> pin is an option? If so, don't you need some fixed time delay
>> properties like max erase time?
>>
>> rb-gpios could be added to the generic nand binding as well.
> The Allwinner NAND controller have dedicated RB pins when NAND is
> enabled, only MUXed with other functions when NAND is not enabled.
>
> Leaving RB unconnected is not a valid hardware configuration. The
> controller internal timing engine depends on being able to sense RB to
> sequence NAND commands properly.
This is not true (at least in this driver). It was in yuq's driver because
he was using the NFC_WAIT_FLAG ,and in this case the controller wait
for the native R/B pin to be high before considering the CMD is complete.
This driver choose the appropriate way to test the R/B state of the
NAND chip according to what was specified in the DT:
- allwinner,rb: native R/B id. These pins will be used by the NAND
controller to test the R/B state. Only 0 and 1 are valid because the
NAND controller only support 2 R/B pins.
- rb-gpios: gpio used for R/B tests. This is a simple GPIO and will
use the GPIO subsystem to test the R/B pin state.
- none: the NAND base code will wait some time before and send
STATUS cmd to the NAND to check its status.
BTW, the controller supports 8 CS (8 NAND chips), but only have 2 native
R/B pins, this means you'll have to use the GPIO or standard GET_STATUS
method if you connect 3 or more NAND chips.
And for the record, I still think the rb-gpios property (or whatever
common name you choose: nand-rb-gpios ?) should be part of
the generic NAND binding, because other controllers (at least the
atmel one :)) use GPIOs to test R/B state.
Best Regards,
Boris
>
> Regards
> Henrik
>
On 29/01/2014 20:10, Jason Gunthorpe wrote:
> On Wed, Jan 29, 2014 at 03:46:20PM -0300, Ezequiel Garcia wrote:
>
>> After CE# has been pulled high and then transitioned low again, the host
>> should issue a Set Features to select the appropriate asynchronous timing mode.
>> """
> Oh, I had forgot you should do a set feature too
>
> Boris, I think the core core should handle this dance and the driver
> should just implement a call back to change the timing mode on the
> interface..
>
> If I ever get a moment I can work on support for timing setting in the
> mvebu driver, I have boards here with ONFI NAND..
Okay, I'll wait :).
Thanks.
>
> Regards,
> Jason
>
On 29/01/2014 15:34, Boris BREZILLON wrote:
> Add support for the sunxi NAND Flash Controller (NFC).
>
> Signed-off-by: Boris BREZILLON <[email protected]>
> ---
> drivers/mtd/nand/Kconfig | 6 +
> drivers/mtd/nand/Makefile | 1 +
> drivers/mtd/nand/sunxi_nand.c | 744 +++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 751 insertions(+)
> create mode 100644 drivers/mtd/nand/sunxi_nand.c
>
> diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig
> index 93ae6a6..784dd42 100644
> --- a/drivers/mtd/nand/Kconfig
> +++ b/drivers/mtd/nand/Kconfig
> @@ -510,4 +510,10 @@ config MTD_NAND_XWAY
> Enables support for NAND Flash chips on Lantiq XWAY SoCs. NAND is attached
> to the External Bus Unit (EBU).
>
> +config MTD_NAND_SUNXI
> + tristate "Support for NAND on Allwinner SoCs"
> + depends on ARCH_SUNXI
> + help
> + Enables support for NAND Flash chips on Allwinner SoCs.
> +
> endif # MTD_NAND
> diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile
> index bbea7a6..e3b4a34 100644
> --- a/drivers/mtd/nand/Makefile
> +++ b/drivers/mtd/nand/Makefile
> @@ -49,5 +49,6 @@ obj-$(CONFIG_MTD_NAND_JZ4740) += jz4740_nand.o
> obj-$(CONFIG_MTD_NAND_GPMI_NAND) += gpmi-nand/
> obj-$(CONFIG_MTD_NAND_XWAY) += xway_nand.o
> obj-$(CONFIG_MTD_NAND_BCM47XXNFLASH) += bcm47xxnflash/
> +obj-$(CONFIG_MTD_NAND_SUNXI) += sunxi_nand.o
>
> nand-objs := nand_base.o nand_bbt.o
> diff --git a/drivers/mtd/nand/sunxi_nand.c b/drivers/mtd/nand/sunxi_nand.c
> new file mode 100644
> index 0000000..d3da810
> --- /dev/null
> +++ b/drivers/mtd/nand/sunxi_nand.c
> @@ -0,0 +1,744 @@
> +/*
> + * Copyright (C) 2013 Boris BREZILLON <[email protected]>
> + *
> + * Derived from:
> + * https://github.com/yuq/sunxi-nfc-mtd
> + * Copyright (C) 2013 Qiang Yu <[email protected]>
> + *
> + * https://github.com/hno/Allwinner-Info
> + * Copyright (C) 2013 Henrik Nordström <Henrik Nordström>
> + *
> + * Copyright (C) 2013 Dmitriy B. <[email protected]>
> + * Copyright (C) 2013 Sergey Lapin <[email protected]>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + */
> +
> +#include <linux/dma-mapping.h>
> +#include <linux/slab.h>
> +#include <linux/module.h>
> +#include <linux/moduleparam.h>
> +#include <linux/platform_device.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/of_gpio.h>
> +#include <linux/of_mtd.h>
> +#include <linux/mtd/mtd.h>
> +#include <linux/mtd/nand.h>
> +#include <linux/mtd/partitions.h>
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/dmaengine.h>
> +#include <linux/gpio.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +
> +#define NFC_REG_CTL 0x0000
> +#define NFC_REG_ST 0x0004
> +#define NFC_REG_INT 0x0008
> +#define NFC_REG_TIMING_CTL 0x000C
> +#define NFC_REG_TIMING_CFG 0x0010
> +#define NFC_REG_ADDR_LOW 0x0014
> +#define NFC_REG_ADDR_HIGH 0x0018
> +#define NFC_REG_SECTOR_NUM 0x001C
> +#define NFC_REG_CNT 0x0020
> +#define NFC_REG_CMD 0x0024
> +#define NFC_REG_RCMD_SET 0x0028
> +#define NFC_REG_WCMD_SET 0x002C
> +#define NFC_REG_IO_DATA 0x0030
> +#define NFC_REG_ECC_CTL 0x0034
> +#define NFC_REG_ECC_ST 0x0038
> +#define NFC_REG_DEBUG 0x003C
> +#define NFC_REG_ECC_CNT0 0x0040
> +#define NFC_REG_ECC_CNT1 0x0044
> +#define NFC_REG_ECC_CNT2 0x0048
> +#define NFC_REG_ECC_CNT3 0x004c
> +#define NFC_REG_USER_DATA_BASE 0x0050
> +#define NFC_REG_SPARE_AREA 0x00A0
> +#define NFC_RAM0_BASE 0x0400
> +#define NFC_RAM1_BASE 0x0800
> +
> +/*define bit use in NFC_CTL*/
> +#define NFC_EN (1 << 0)
> +#define NFC_RESET (1 << 1)
> +#define NFC_BUS_WIDYH (1 << 2)
> +#define NFC_RB_SEL (1 << 3)
> +#define NFC_CE_SEL (7 << 24)
> +#define NFC_CE_CTL (1 << 6)
> +#define NFC_CE_CTL1 (1 << 7)
> +#define NFC_PAGE_SIZE (0xf << 8)
> +#define NFC_SAM (1 << 12)
> +#define NFC_RAM_METHOD (1 << 14)
> +#define NFC_DEBUG_CTL (1 << 31)
> +
> +/*define bit use in NFC_ST*/
> +#define NFC_RB_B2R (1 << 0)
> +#define NFC_CMD_INT_FLAG (1 << 1)
> +#define NFC_DMA_INT_FLAG (1 << 2)
> +#define NFC_CMD_FIFO_STATUS (1 << 3)
> +#define NFC_STA (1 << 4)
> +#define NFC_NATCH_INT_FLAG (1 << 5)
> +#define NFC_RB_STATE0 (1 << 8)
> +#define NFC_RB_STATE1 (1 << 9)
> +#define NFC_RB_STATE2 (1 << 10)
> +#define NFC_RB_STATE3 (1 << 11)
> +
> +/*define bit use in NFC_INT*/
> +#define NFC_B2R_INT_ENABLE (1 << 0)
> +#define NFC_CMD_INT_ENABLE (1 << 1)
> +#define NFC_DMA_INT_ENABLE (1 << 2)
> +#define NFC_INT_MASK (NFC_B2R_INT_ENABLE | \
> + NFC_CMD_INT_ENABLE | \
> + NFC_DMA_INT_ENABLE)
> +
> +
> +/*define bit use in NFC_CMD*/
> +#define NFC_CMD_LOW_BYTE (0xff << 0)
> +#define NFC_CMD_HIGH_BYTE (0xff << 8)
> +#define NFC_ADR_NUM (0x7 << 16)
> +#define NFC_SEND_ADR (1 << 19)
> +#define NFC_ACCESS_DIR (1 << 20)
> +#define NFC_DATA_TRANS (1 << 21)
> +#define NFC_SEND_CMD1 (1 << 22)
> +#define NFC_WAIT_FLAG (1 << 23)
> +#define NFC_SEND_CMD2 (1 << 24)
> +#define NFC_SEQ (1 << 25)
> +#define NFC_DATA_SWAP_METHOD (1 << 26)
> +#define NFC_ROW_AUTO_INC (1 << 27)
> +#define NFC_SEND_CMD3 (1 << 28)
> +#define NFC_SEND_CMD4 (1 << 29)
> +#define NFC_CMD_TYPE (3 << 30)
> +
> +/* define bit use in NFC_RCMD_SET*/
> +#define NFC_READ_CMD (0xff << 0)
> +#define NFC_RANDOM_READ_CMD0 (0xff << 8)
> +#define NFC_RANDOM_READ_CMD1 (0xff << 16)
> +
> +/*define bit use in NFC_WCMD_SET*/
> +#define NFC_PROGRAM_CMD (0xff << 0)
> +#define NFC_RANDOM_WRITE_CMD (0xff << 8)
> +#define NFC_READ_CMD0 (0xff << 16)
> +#define NFC_READ_CMD1 (0xff << 24)
> +
> +/*define bit use in NFC_ECC_CTL*/
> +#define NFC_ECC_EN (1 << 0)
> +#define NFC_ECC_PIPELINE (1 << 3)
> +#define NFC_ECC_EXCEPTION (1 << 4)
> +#define NFC_ECC_BLOCK_SIZE (1 << 5)
> +#define NFC_RANDOM_EN (1 << 9)
> +#define NFC_RANDOM_DIRECTION (1 << 10)
> +#define NFC_ECC_MODE_SHIFT 12
> +#define NFC_ECC_MODE (0xf << NFC_ECC_MODE_SHIFT)
> +#define NFC_RANDOM_SEED (0x7fff << 16)
> +
> +
> +
> +enum sunxi_nand_rb_type {
> + RB_NONE,
> + RB_NATIVE,
> + RB_GPIO,
> +};
> +
> +struct sunxi_nand_rb {
> + enum sunxi_nand_rb_type type;
> + union {
> + int gpio;
> + int nativeid;
> + } info;
> +};
> +
> +struct sunxi_nand_chip_sel {
> + u8 cs;
> + struct sunxi_nand_rb rb;
> +};
> +
> +#define DEFAULT_NAME_FORMAT "nand@%d"
> +#define MAX_NAME_SIZE (sizeof("nand@") + 2)
> +
> +struct sunxi_nand_chip {
> + struct list_head node;
> + struct nand_chip nand;
> + struct mtd_info mtd;
> + char default_name[MAX_NAME_SIZE];
> + unsigned long clk_rate;
> + int selected;
> + int nsels;
> + struct sunxi_nand_chip_sel sels[0];
> +};
> +
> +static inline struct sunxi_nand_chip *to_sunxi_nand(struct mtd_info *mtd)
> +{
> + return container_of(mtd, struct sunxi_nand_chip, mtd);
> +}
> +
> +struct sunxi_nfc {
> + struct nand_hw_control controller;
> + void __iomem *regs;
> + int irq;
> + struct clk *ahb_clk;
> + struct clk *sclk;
> + unsigned long assigned_cs;
> + unsigned long clk_rate;
> + struct list_head chips;
> + struct completion complete;
> +};
> +
> +static inline struct sunxi_nfc *to_sunxi_nfc(struct nand_hw_control *ctrl)
> +{
> + return container_of(ctrl, struct sunxi_nfc, controller);
> +}
> +
> +static irqreturn_t sunxi_nfc_interrupt(int irq, void *dev_id)
> +{
> + struct sunxi_nfc *nfc = dev_id;
> + u32 st = readl(nfc->regs + NFC_REG_ST);
> + u32 ien = readl(nfc->regs + NFC_REG_INT);
> +
> + if (!(ien & st))
> + return IRQ_NONE;
> +
> + if ((ien & st) == ien)
> + complete(&nfc->complete);
> +
> + writel(st & NFC_INT_MASK, nfc->regs + NFC_REG_ST);
> + writel(~st & ien & NFC_INT_MASK, nfc->regs + NFC_REG_INT);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int sunxi_nfc_wait_int(struct sunxi_nfc *nfc, u32 flags,
> + unsigned int timeout_ms)
> +{
> + init_completion(&nfc->complete);
> +
> + writel(flags, nfc->regs + NFC_REG_INT);
> + if (!timeout_ms)
> + wait_for_completion(&nfc->complete);
> + else if (!wait_for_completion_timeout(&nfc->complete,
> + msecs_to_jiffies(timeout_ms)))
> + return -ETIMEDOUT;
> +
> + return 0;
> +}
> +
> +static int sunxi_nfc_dev_ready(struct mtd_info *mtd)
> +{
> + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(mtd);
> + struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller);
> + struct sunxi_nand_rb *rb;
> + unsigned long timeo = (sunxi_nand->nand.state == FL_ERASING ? 400 : 20);
> + int ret;
> +
> + if (sunxi_nand->selected < 0)
> + return 0;
> +
> + rb = &sunxi_nand->sels[sunxi_nand->selected].rb;
> +
> + switch (rb->type) {
> + case RB_NATIVE:
> + ret = !!(readl(nfc->regs + NFC_REG_ST) &
> + (NFC_RB_STATE0 << rb->info.nativeid));
> + if (ret)
> + break;
> +
> + sunxi_nfc_wait_int(nfc, NFC_RB_B2R, timeo);
> + ret = !!(readl(nfc->regs + NFC_REG_ST) &
> + (NFC_RB_STATE0 << rb->info.nativeid));
> + break;
> + case RB_GPIO:
> + ret = gpio_get_value(rb->info.gpio);
> + break;
> + case RB_NONE:
> + default:
> + ret = 0;
> + dev_err(&mtd->dev, "cannot check R/B NAND status!");
> + break;
> + }
> +
> + return ret;
> +}
> +
> +static void sunxi_nfc_select_chip(struct mtd_info *mtd, int chip)
> +{
> + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(mtd);
> + struct nand_chip *nand = &sunxi_nand->nand;
> + struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller);
> + struct sunxi_nand_chip_sel *sel;
> + u32 ctl;
> +
> + if (chip > 0 && chip >= sunxi_nand->nsels)
> + return;
> +
> + if (chip == sunxi_nand->selected)
> + return;
> +
> + ctl = readl(nfc->regs + NFC_REG_CTL) &
> + ~(NFC_CE_SEL | NFC_RB_SEL | NFC_EN);
> +
> + if (chip >= 0) {
> + sel = &sunxi_nand->sels[chip];
> +
> + ctl |= (sel->cs << 24) | NFC_EN |
> + (((nand->page_shift - 10) & 0xf) << 8);
> + if (sel->rb.type == RB_NONE) {
> + nand->dev_ready = NULL;
> + } else {
> + nand->dev_ready = sunxi_nfc_dev_ready;
> + if (sel->rb.type == RB_NATIVE)
> + ctl |= (sel->rb.info.nativeid << 3);
> + }
> +
> + writel(mtd->writesize, nfc->regs + NFC_REG_SPARE_AREA);
> +
> + if (nfc->clk_rate != sunxi_nand->clk_rate) {
> + clk_set_rate(nfc->sclk, sunxi_nand->clk_rate);
> + nfc->clk_rate = sunxi_nand->clk_rate;
> + }
> + }
> +
> + writel(ctl, nfc->regs + NFC_REG_CTL);
> +
> + sunxi_nand->selected = chip;
> +}
> +
> +static void sunxi_nfc_read_buf(struct mtd_info *mtd, uint8_t *buf, int len)
> +{
> + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(mtd);
> + struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller);
> + int cnt;
> + int offs = 0;
> + u32 tmp;
> +
> + while (len > offs) {
> + cnt = len - offs;
> + if (cnt > 1024)
> + cnt = 1024;
> +
> + while ((readl(nfc->regs + NFC_REG_ST) & NFC_CMD_FIFO_STATUS))
> + ;
> + writel(cnt, nfc->regs + NFC_REG_CNT);
> + tmp = NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD;
> + writel(tmp, nfc->regs + NFC_REG_CMD);
> + sunxi_nfc_wait_int(nfc, NFC_CMD_INT_FLAG, 0);
> + if (buf)
> + memcpy_fromio(buf + offs, nfc->regs + NFC_RAM0_BASE,
> + cnt);
> + offs += cnt;
> + }
> +}
> +
> +static void sunxi_nfc_write_buf(struct mtd_info *mtd, const uint8_t *buf,
> + int len)
> +{
> + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(mtd);
> + struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller);
> + int cnt;
> + int offs = 0;
> + u32 tmp;
> +
> + while (len > offs) {
> + cnt = len - offs;
> + if (cnt > 1024)
> + cnt = 1024;
> +
> + while ((readl(nfc->regs + NFC_REG_ST) & NFC_CMD_FIFO_STATUS))
> + ;
> + writel(cnt, nfc->regs + NFC_REG_CNT);
> + memcpy_toio(nfc->regs + NFC_RAM0_BASE, buf + offs, cnt);
> + tmp = NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD |
> + NFC_ACCESS_DIR;
> + writel(tmp, nfc->regs + NFC_REG_CMD);
> + sunxi_nfc_wait_int(nfc, NFC_CMD_INT_FLAG, 0);
> + offs += cnt;
> + }
> +}
> +
> +static uint8_t sunxi_nfc_read_byte(struct mtd_info *mtd)
> +{
> + uint8_t ret;
> +
> + sunxi_nfc_read_buf(mtd, &ret, 1);
> +
> + return ret;
> +}
> +
> +static void sunxi_nfc_cmd_ctrl(struct mtd_info *mtd, int dat,
> + unsigned int ctrl)
> +{
> + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(mtd);
> + struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller);
> + u32 tmp;
> +
> + while ((readl(nfc->regs + NFC_REG_ST) & NFC_CMD_FIFO_STATUS))
> + ;
> +
> + if (ctrl & NAND_CTRL_CHANGE) {
> + tmp = readl(nfc->regs + NFC_REG_CTL);
> + if (ctrl & NAND_NCE)
> + tmp |= NFC_CE_CTL;
> + else
> + tmp &= ~NFC_CE_CTL;
> + writel(tmp, nfc->regs + NFC_REG_CTL);
> + }
> +
> + if (dat == NAND_CMD_NONE)
> + return;
> +
> + if (ctrl & NAND_CLE) {
> + writel(NFC_SEND_CMD1 | dat, nfc->regs + NFC_REG_CMD);
> + } else {
> + writel(dat, nfc->regs + NFC_REG_ADDR_LOW);
> + writel(NFC_SEND_ADR, nfc->regs + NFC_REG_CMD);
> + }
> +
> + sunxi_nfc_wait_int(nfc, NFC_CMD_INT_FLAG, 0);
> +}
> +
> +static int sunxi_nand_chip_init_timings(struct sunxi_nand_chip *chip,
> + struct device_node *np)
> +{
> + const struct nand_sdr_timings *timings;
> + u32 min_clk_period = 0;
> + int ret;
> +
> + ret = onfi_get_async_timing_mode(&chip->nand);
> + if (ret == ONFI_TIMING_MODE_UNKNOWN) {
> + ret = of_get_nand_onfi_timing_mode(np);
> + if (ret < 0)
> + return ret;
> + }
> +
> + ret = fls(ret);
> + if (!ret)
> + return -EINVAL;
> +
> + timings = onfi_async_timing_mode_to_sdr_timings(ret - 1);
> + if (IS_ERR(timings))
> + return PTR_ERR(timings);
> +
> + /* NFC timings defined in Allwinner Datasheets */
> +
> + /* T1 <=> tCLS */
> + if (timings->tCLS_min > min_clk_period)
> + min_clk_period = timings->tCLS_min;
> +
> + /* T2 <=> tCLH */
> + if (timings->tCLH_min > min_clk_period)
> + min_clk_period = timings->tCLH_min;
> +
> + /* T3 <=> tCS */
> + if (timings->tCS_min > min_clk_period)
> + min_clk_period = timings->tCS_min;
> +
> + /* T4 <=> tCH */
> + if (timings->tCH_min > min_clk_period)
> + min_clk_period = timings->tCH_min;
> +
> + /* T5 <=> tWP */
> + if (timings->tWP_min > min_clk_period)
> + min_clk_period = timings->tWP_min;
> +
> + /* T6 <=> tWH */
> + if (timings->tWH_min > min_clk_period)
> + min_clk_period = timings->tWH_min;
> +
> + /* T7 <=> tALS */
> + if (timings->tALS_min > min_clk_period)
> + min_clk_period = timings->tALS_min;
> +
> + /* T8 <=> tDS */
> + if (timings->tDS_min > min_clk_period)
> + min_clk_period = timings->tDS_min;
> +
> + /* T9 <=> tDH */
> + if (timings->tDH_min > min_clk_period)
> + min_clk_period = timings->tDH_min;
> +
> + /* T10 <=> tRR */
> + if (timings->tRR_min > (min_clk_period * 3))
> + min_clk_period = (timings->tRR_min + 2) / 3;
> +
> + /* T11 <=> tALH */
> + if (timings->tALH_min > min_clk_period)
> + min_clk_period = timings->tALH_min;
> +
> + /* T12 <=> tRP */
> + if (timings->tRP_min > min_clk_period)
> + min_clk_period = timings->tRP_min;
> +
> + /* T13 <=> tREH */
> + if (timings->tREH_min > min_clk_period)
> + min_clk_period = timings->tREH_min;
> +
> + /* T14 <=> tRC */
> + if (timings->tRC_min > (min_clk_period * 2))
> + min_clk_period = (timings->tRC_min + 1) / 2;
> +
> + /* T15 <=> tWC */
> + if (timings->tWC_min > (min_clk_period * 2))
> + min_clk_period = (timings->tWC_min + 1) / 2;
> +
> +
> + /* min_clk_period = (NAND-clk-period * 2) */
> + if (!min_clk_period) {
> + chip->clk_rate = 20000000;
> + } else {
> + min_clk_period /= 1000;
> + if (!min_clk_period)
> + min_clk_period = 1;
> + chip->clk_rate = (2 * 1000000000) / min_clk_period;
> + }
> +
> + /* TODO: configure T16-T19 */
> +
> + return 0;
> +}
> +
> +static int sunxi_nand_chip_init(struct device *dev, struct sunxi_nfc *nfc,
> + struct device_node *np)
> +{
> + struct sunxi_nand_chip *chip;
> + struct mtd_part_parser_data ppdata;
> + struct mtd_info *mtd;
> + struct nand_chip *nand;
> + u32 strength;
> + u32 blk_size;
> + int nsels;
> + int ret;
> + int i;
> + u32 tmp;
> +
> + if (!of_get_property(np, "reg", &nsels))
> + return -EINVAL;
> +
> + nsels /= sizeof(u32);
> + if (!nsels)
> + return -EINVAL;
> +
> + chip = devm_kzalloc(dev,
> + sizeof(*chip) +
> + (nsels * sizeof(struct sunxi_nand_chip_sel)),
> + GFP_KERNEL);
> + if (!chip)
> + return -ENOMEM;
> +
> + chip->nsels = nsels;
> + chip->selected = -1;
> +
> + for (i = 0; i < nsels; i++) {
> + ret = of_property_read_u32_index(np, "reg", i, &tmp);
> + if (ret)
> + return ret;
> +
> + if (tmp > 7)
> + return -EINVAL;
> +
> + if (test_and_set_bit(tmp, &nfc->assigned_cs))
> + return -EINVAL;
> +
> + chip->sels[i].cs = tmp;
> +
> + if (!of_property_read_u32_index(np, "allwinner,rb", i, &tmp) &&
> + tmp < 2) {
> + chip->sels[i].rb.type = RB_NATIVE;
> + chip->sels[i].rb.info.nativeid = tmp;
> + } else {
> + ret = of_get_named_gpio(np, "rb-gpios", i);
> + if (ret >= 0) {
> + chip->sels[i].rb.type = RB_GPIO;
> + chip->sels[i].rb.info.gpio = tmp;
> + ret = devm_gpio_request(dev, tmp, "nand-rb");
> + if (ret)
> + return ret;
While testing the GPIO handling of R/B state, I found 2 bugs:
- the GPIO number is stored in ret not tmp
- we need to configure the GPIO as an input
Here's a patch fixing these bugs:
diff --git a/drivers/mtd/nand/sunxi_nand.c b/drivers/mtd/nand/sunxi_nand.c
index 7e1cefc..41fb3b8 100644
--- a/drivers/mtd/nand/sunxi_nand.c
+++ b/drivers/mtd/nand/sunxi_nand.c
@@ -814,11 +814,16 @@ static int sunxi_nand_chip_init(struct device
*dev, struct sunxi_nfc *nfc,
} else {
ret = of_get_named_gpio(np, "rb-gpios", i);
if (ret >= 0) {
+ tmp = ret;
chip->sels[i].rb.type = RB_GPIO;
chip->sels[i].rb.info.gpio = tmp;
ret = devm_gpio_request(dev, tmp,
"nand-rb");
if (ret)
return ret;
+
+ ret = gpio_direction_input(tmp);
+ if (ret)
+ return ret;
} else {
chip->sels[i].rb.type = RB_NONE;
}
I'll fix it for the next version.
> + } else {
> + chip->sels[i].rb.type = RB_NONE;
> + }
> + }
> + }
> +
> + ret = sunxi_nand_chip_init_timings(chip, np);
> + if (ret)
> + return ret;
> +
> + nand = &chip->nand;
> + nand->controller = &nfc->controller;
> + nand->select_chip = sunxi_nfc_select_chip;
> + nand->cmd_ctrl = sunxi_nfc_cmd_ctrl;
> + nand->read_buf = sunxi_nfc_read_buf;
> + nand->write_buf = sunxi_nfc_write_buf;
> + nand->read_byte = sunxi_nfc_read_byte;
> +
> + nand->ecc.mode = of_get_nand_ecc_mode(np);
> + if (of_get_nand_on_flash_bbt(np))
> + nand->bbt_options |= NAND_BBT_USE_FLASH;
> +
> + mtd = &chip->mtd;
> + mtd->priv = nand;
> + mtd->owner = THIS_MODULE;
> +
> + ret = nand_scan_ident(mtd, nsels, NULL);
> + if (ret)
> + return ret;
> +
> + if (nand->ecc.mode == NAND_ECC_SOFT_BCH) {
> + if (!of_get_nand_ecc_level(np, &strength, &blk_size)) {
> + nand->ecc_step_ds = blk_size;
> + nand->ecc_strength_ds = strength;
> + }
> +
> + nand->ecc.size = nand->ecc_step_ds;
> + nand->ecc.bytes = (((nand->ecc_strength_ds *
> + fls(8 * nand->ecc_step_ds)) + 7) / 8);
> + }
> +
> + ret = nand_scan_tail(mtd);
> + if (ret)
> + return ret;
> +
> + if (of_property_read_string(np, "nand-name", &mtd->name)) {
> + snprintf(chip->default_name, MAX_NAME_SIZE,
> + DEFAULT_NAME_FORMAT, chip->sels[i].cs);
> + mtd->name = chip->default_name;
> + }
> +
> + ppdata.of_node = np;
> + ret = mtd_device_parse_register(mtd, NULL, &ppdata, NULL, 0);
> + if (!ret)
> + return ret;
> +
> + list_add_tail(&chip->node, &nfc->chips);
> +
> + return 0;
> +}
> +
> +static int sunxi_nand_chips_init(struct device *dev, struct sunxi_nfc *nfc)
> +{
> + struct device_node *np = dev->of_node;
> + struct device_node *nand_np;
> + int nchips = of_get_child_count(np);
> + int ret;
> +
> + if (nchips > 8)
> + return -EINVAL;
> +
> + for_each_child_of_node(np, nand_np) {
> + ret = sunxi_nand_chip_init(dev, nfc, nand_np);
> + if (ret)
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int sunxi_nfc_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct resource *r;
> + struct sunxi_nfc *nfc;
> + int ret;
> +
> + nfc = devm_kzalloc(dev, sizeof(*nfc), GFP_KERNEL);
> + if (!nfc) {
> + dev_err(dev, "failed to allocate NFC struct\n");
> + return -ENOMEM;
> + }
> +
> + spin_lock_init(&nfc->controller.lock);
> + init_waitqueue_head(&nfc->controller.wq);
> + INIT_LIST_HEAD(&nfc->chips);
> +
> + r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + nfc->regs = devm_ioremap_resource(dev, r);
> + if (IS_ERR(nfc->regs)) {
> + dev_err(dev, "failed to remap iomem\n");
> + return PTR_ERR(nfc->regs);
> + }
> +
> + nfc->irq = platform_get_irq(pdev, 0);
> + if (nfc->irq < 0) {
> + dev_err(dev, "failed to retrieve irq\n");
> + return nfc->irq;
> + }
> +
> + nfc->ahb_clk = devm_clk_get(dev, "ahb_clk");
> + if (IS_ERR(nfc->ahb_clk)) {
> + dev_err(dev, "failed to retrieve ahb_clk\n");
> + return PTR_ERR(nfc->ahb_clk);
> + }
> +
> + ret = clk_prepare_enable(nfc->ahb_clk);
> + if (ret)
> + return ret;
> +
> + nfc->sclk = devm_clk_get(dev, "sclk");
> + if (IS_ERR(nfc->sclk)) {
> + dev_err(dev, "failed to retrieve nand_clk\n");
> + ret = PTR_ERR(nfc->sclk);
> + goto out_ahb_clk_unprepare;
> + }
> +
> + ret = clk_prepare_enable(nfc->sclk);
> + if (ret)
> + goto out_ahb_clk_unprepare;
> +
> + /* Reset NFC */
> + writel(readl(nfc->regs + NFC_REG_CTL) | NFC_RESET,
> + nfc->regs + NFC_REG_CTL);
> + while (readl(nfc->regs + NFC_REG_CTL) & NFC_RESET)
> + ;
> +
> + writel(0, nfc->regs + NFC_REG_INT);
> + ret = devm_request_irq(dev, nfc->irq, sunxi_nfc_interrupt,
> + 0, "sunxi-nand", nfc);
> + if (ret)
> + goto out_sclk_unprepare;
> +
> + platform_set_drvdata(pdev, nfc);
> +
> + writel(0x100, nfc->regs + NFC_REG_TIMING_CTL);
> + writel(0x7ff, nfc->regs + NFC_REG_TIMING_CFG);
> +
> + ret = sunxi_nand_chips_init(dev, nfc);
> + if (ret) {
> + dev_err(dev, "failed to init nand chips\n");
> + goto out_sclk_unprepare;
> + }
> +
> + return 0;
> +
> +out_sclk_unprepare:
> + clk_disable_unprepare(nfc->sclk);
> +out_ahb_clk_unprepare:
> + clk_disable_unprepare(nfc->ahb_clk);
> +
> + return ret;
> +}
> +
> +static const struct of_device_id sunxi_nfc_ids[] = {
> + { .compatible = "allwinner,sun4i-nand" },
> + { /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, sunxi_nfc_ids);
> +
> +static struct platform_driver sunxi_nfc_driver = {
> + .driver = {
> + .name = "sunxi_nand",
> + .owner = THIS_MODULE,
> + .of_match_table = of_match_ptr(sunxi_nfc_ids),
> + },
> + .probe = sunxi_nfc_probe,
> +};
> +module_platform_driver(sunxi_nfc_driver);
> +
> +MODULE_LICENSE("GPL v2");
> +MODULE_AUTHOR("Boris BREZILLON");
> +MODULE_DESCRIPTION("Allwinner NAND Flash Controller driver");
> +MODULE_ALIAS("platform:sunxi_nfc");
Add support for the sunxi NAND Flash Controller (NFC).
Signed-off-by: Boris BREZILLON <[email protected]>
---
Hello,
This version fixes a bug in the R/B GPIO config block.
The timing config order is now respected, but I'll wait for Jason work
regarding timing config in NAND core code before posting the 3rd version
of this series.
Best Regards,
Boris
Changes since v2:
- fix R/B GPIO retrieval/config bug
- fix timings configuration order (set mode 0 -> scan -> set best supported
mode)
drivers/mtd/nand/Kconfig | 6 +
drivers/mtd/nand/Makefile | 1 +
drivers/mtd/nand/sunxi_nand.c | 758 +++++++++++++++++++++++++++++++++++++++++
3 files changed, 765 insertions(+)
create mode 100644 drivers/mtd/nand/sunxi_nand.c
diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig
index 93ae6a6..784dd42 100644
--- a/drivers/mtd/nand/Kconfig
+++ b/drivers/mtd/nand/Kconfig
@@ -510,4 +510,10 @@ config MTD_NAND_XWAY
Enables support for NAND Flash chips on Lantiq XWAY SoCs. NAND is attached
to the External Bus Unit (EBU).
+config MTD_NAND_SUNXI
+ tristate "Support for NAND on Allwinner SoCs"
+ depends on ARCH_SUNXI
+ help
+ Enables support for NAND Flash chips on Allwinner SoCs.
+
endif # MTD_NAND
diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile
index bbea7a6..e3b4a34 100644
--- a/drivers/mtd/nand/Makefile
+++ b/drivers/mtd/nand/Makefile
@@ -49,5 +49,6 @@ obj-$(CONFIG_MTD_NAND_JZ4740) += jz4740_nand.o
obj-$(CONFIG_MTD_NAND_GPMI_NAND) += gpmi-nand/
obj-$(CONFIG_MTD_NAND_XWAY) += xway_nand.o
obj-$(CONFIG_MTD_NAND_BCM47XXNFLASH) += bcm47xxnflash/
+obj-$(CONFIG_MTD_NAND_SUNXI) += sunxi_nand.o
nand-objs := nand_base.o nand_bbt.o
diff --git a/drivers/mtd/nand/sunxi_nand.c b/drivers/mtd/nand/sunxi_nand.c
new file mode 100644
index 0000000..1014b2a
--- /dev/null
+++ b/drivers/mtd/nand/sunxi_nand.c
@@ -0,0 +1,758 @@
+/*
+ * Copyright (C) 2013 Boris BREZILLON <[email protected]>
+ *
+ * Derived from:
+ * https://github.com/yuq/sunxi-nfc-mtd
+ * Copyright (C) 2013 Qiang Yu <[email protected]>
+ *
+ * https://github.com/hno/Allwinner-Info
+ * Copyright (C) 2013 Henrik Nordström <Henrik Nordström>
+ *
+ * Copyright (C) 2013 Dmitriy B. <[email protected]>
+ * Copyright (C) 2013 Sergey Lapin <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_gpio.h>
+#include <linux/of_mtd.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/partitions.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/dmaengine.h>
+#include <linux/gpio.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+
+#define NFC_REG_CTL 0x0000
+#define NFC_REG_ST 0x0004
+#define NFC_REG_INT 0x0008
+#define NFC_REG_TIMING_CTL 0x000C
+#define NFC_REG_TIMING_CFG 0x0010
+#define NFC_REG_ADDR_LOW 0x0014
+#define NFC_REG_ADDR_HIGH 0x0018
+#define NFC_REG_SECTOR_NUM 0x001C
+#define NFC_REG_CNT 0x0020
+#define NFC_REG_CMD 0x0024
+#define NFC_REG_RCMD_SET 0x0028
+#define NFC_REG_WCMD_SET 0x002C
+#define NFC_REG_IO_DATA 0x0030
+#define NFC_REG_ECC_CTL 0x0034
+#define NFC_REG_ECC_ST 0x0038
+#define NFC_REG_DEBUG 0x003C
+#define NFC_REG_ECC_CNT0 0x0040
+#define NFC_REG_ECC_CNT1 0x0044
+#define NFC_REG_ECC_CNT2 0x0048
+#define NFC_REG_ECC_CNT3 0x004c
+#define NFC_REG_USER_DATA_BASE 0x0050
+#define NFC_REG_SPARE_AREA 0x00A0
+#define NFC_RAM0_BASE 0x0400
+#define NFC_RAM1_BASE 0x0800
+
+/*define bit use in NFC_CTL*/
+#define NFC_EN (1 << 0)
+#define NFC_RESET (1 << 1)
+#define NFC_BUS_WIDYH (1 << 2)
+#define NFC_RB_SEL (1 << 3)
+#define NFC_CE_SEL (7 << 24)
+#define NFC_CE_CTL (1 << 6)
+#define NFC_CE_CTL1 (1 << 7)
+#define NFC_PAGE_SIZE (0xf << 8)
+#define NFC_SAM (1 << 12)
+#define NFC_RAM_METHOD (1 << 14)
+#define NFC_DEBUG_CTL (1 << 31)
+
+/*define bit use in NFC_ST*/
+#define NFC_RB_B2R (1 << 0)
+#define NFC_CMD_INT_FLAG (1 << 1)
+#define NFC_DMA_INT_FLAG (1 << 2)
+#define NFC_CMD_FIFO_STATUS (1 << 3)
+#define NFC_STA (1 << 4)
+#define NFC_NATCH_INT_FLAG (1 << 5)
+#define NFC_RB_STATE0 (1 << 8)
+#define NFC_RB_STATE1 (1 << 9)
+#define NFC_RB_STATE2 (1 << 10)
+#define NFC_RB_STATE3 (1 << 11)
+
+/*define bit use in NFC_INT*/
+#define NFC_B2R_INT_ENABLE (1 << 0)
+#define NFC_CMD_INT_ENABLE (1 << 1)
+#define NFC_DMA_INT_ENABLE (1 << 2)
+#define NFC_INT_MASK (NFC_B2R_INT_ENABLE | \
+ NFC_CMD_INT_ENABLE | \
+ NFC_DMA_INT_ENABLE)
+
+
+/*define bit use in NFC_CMD*/
+#define NFC_CMD_LOW_BYTE (0xff << 0)
+#define NFC_CMD_HIGH_BYTE (0xff << 8)
+#define NFC_ADR_NUM (0x7 << 16)
+#define NFC_SEND_ADR (1 << 19)
+#define NFC_ACCESS_DIR (1 << 20)
+#define NFC_DATA_TRANS (1 << 21)
+#define NFC_SEND_CMD1 (1 << 22)
+#define NFC_WAIT_FLAG (1 << 23)
+#define NFC_SEND_CMD2 (1 << 24)
+#define NFC_SEQ (1 << 25)
+#define NFC_DATA_SWAP_METHOD (1 << 26)
+#define NFC_ROW_AUTO_INC (1 << 27)
+#define NFC_SEND_CMD3 (1 << 28)
+#define NFC_SEND_CMD4 (1 << 29)
+#define NFC_CMD_TYPE (3 << 30)
+
+/* define bit use in NFC_RCMD_SET*/
+#define NFC_READ_CMD (0xff << 0)
+#define NFC_RANDOM_READ_CMD0 (0xff << 8)
+#define NFC_RANDOM_READ_CMD1 (0xff << 16)
+
+/*define bit use in NFC_WCMD_SET*/
+#define NFC_PROGRAM_CMD (0xff << 0)
+#define NFC_RANDOM_WRITE_CMD (0xff << 8)
+#define NFC_READ_CMD0 (0xff << 16)
+#define NFC_READ_CMD1 (0xff << 24)
+
+/*define bit use in NFC_ECC_CTL*/
+#define NFC_ECC_EN (1 << 0)
+#define NFC_ECC_PIPELINE (1 << 3)
+#define NFC_ECC_EXCEPTION (1 << 4)
+#define NFC_ECC_BLOCK_SIZE (1 << 5)
+#define NFC_RANDOM_EN (1 << 9)
+#define NFC_RANDOM_DIRECTION (1 << 10)
+#define NFC_ECC_MODE_SHIFT 12
+#define NFC_ECC_MODE (0xf << NFC_ECC_MODE_SHIFT)
+#define NFC_RANDOM_SEED (0x7fff << 16)
+
+
+
+enum sunxi_nand_rb_type {
+ RB_NONE,
+ RB_NATIVE,
+ RB_GPIO,
+};
+
+struct sunxi_nand_rb {
+ enum sunxi_nand_rb_type type;
+ union {
+ int gpio;
+ int nativeid;
+ } info;
+};
+
+struct sunxi_nand_chip_sel {
+ u8 cs;
+ struct sunxi_nand_rb rb;
+};
+
+#define DEFAULT_NAME_FORMAT "nand@%d"
+#define MAX_NAME_SIZE (sizeof("nand@") + 2)
+
+struct sunxi_nand_chip {
+ struct list_head node;
+ struct nand_chip nand;
+ struct mtd_info mtd;
+ char default_name[MAX_NAME_SIZE];
+ unsigned long clk_rate;
+ int selected;
+ int nsels;
+ struct sunxi_nand_chip_sel sels[0];
+};
+
+static inline struct sunxi_nand_chip *to_sunxi_nand(struct mtd_info *mtd)
+{
+ return container_of(mtd, struct sunxi_nand_chip, mtd);
+}
+
+struct sunxi_nfc {
+ struct nand_hw_control controller;
+ void __iomem *regs;
+ int irq;
+ struct clk *ahb_clk;
+ struct clk *sclk;
+ unsigned long assigned_cs;
+ unsigned long clk_rate;
+ struct list_head chips;
+ struct completion complete;
+};
+
+static inline struct sunxi_nfc *to_sunxi_nfc(struct nand_hw_control *ctrl)
+{
+ return container_of(ctrl, struct sunxi_nfc, controller);
+}
+
+static irqreturn_t sunxi_nfc_interrupt(int irq, void *dev_id)
+{
+ struct sunxi_nfc *nfc = dev_id;
+ u32 st = readl(nfc->regs + NFC_REG_ST);
+ u32 ien = readl(nfc->regs + NFC_REG_INT);
+
+ if (!(ien & st))
+ return IRQ_NONE;
+
+ if ((ien & st) == ien)
+ complete(&nfc->complete);
+
+ writel(st & NFC_INT_MASK, nfc->regs + NFC_REG_ST);
+ writel(~st & ien & NFC_INT_MASK, nfc->regs + NFC_REG_INT);
+
+ return IRQ_HANDLED;
+}
+
+static int sunxi_nfc_wait_int(struct sunxi_nfc *nfc, u32 flags,
+ unsigned int timeout_ms)
+{
+ init_completion(&nfc->complete);
+
+ writel(flags, nfc->regs + NFC_REG_INT);
+ if (!timeout_ms)
+ wait_for_completion(&nfc->complete);
+ else if (!wait_for_completion_timeout(&nfc->complete,
+ msecs_to_jiffies(timeout_ms)))
+ return -ETIMEDOUT;
+
+ return 0;
+}
+
+static int sunxi_nfc_dev_ready(struct mtd_info *mtd)
+{
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(mtd);
+ struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller);
+ struct sunxi_nand_rb *rb;
+ unsigned long timeo = (sunxi_nand->nand.state == FL_ERASING ? 400 : 20);
+ int ret;
+
+ if (sunxi_nand->selected < 0)
+ return 0;
+
+ rb = &sunxi_nand->sels[sunxi_nand->selected].rb;
+
+ switch (rb->type) {
+ case RB_NATIVE:
+ ret = !!(readl(nfc->regs + NFC_REG_ST) &
+ (NFC_RB_STATE0 << rb->info.nativeid));
+ if (ret)
+ break;
+
+ sunxi_nfc_wait_int(nfc, NFC_RB_B2R, timeo);
+ ret = !!(readl(nfc->regs + NFC_REG_ST) &
+ (NFC_RB_STATE0 << rb->info.nativeid));
+ break;
+ case RB_GPIO:
+ ret = gpio_get_value(rb->info.gpio);
+ break;
+ case RB_NONE:
+ default:
+ ret = 0;
+ dev_err(&mtd->dev, "cannot check R/B NAND status!");
+ break;
+ }
+
+ return ret;
+}
+
+static void sunxi_nfc_select_chip(struct mtd_info *mtd, int chip)
+{
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(mtd);
+ struct nand_chip *nand = &sunxi_nand->nand;
+ struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller);
+ struct sunxi_nand_chip_sel *sel;
+ u32 ctl;
+
+ if (chip > 0 && chip >= sunxi_nand->nsels)
+ return;
+
+ if (chip == sunxi_nand->selected)
+ return;
+
+ ctl = readl(nfc->regs + NFC_REG_CTL) &
+ ~(NFC_CE_SEL | NFC_RB_SEL | NFC_EN);
+
+ if (chip >= 0) {
+ sel = &sunxi_nand->sels[chip];
+
+ ctl |= (sel->cs << 24) | NFC_EN |
+ (((nand->page_shift - 10) & 0xf) << 8);
+ if (sel->rb.type == RB_NONE) {
+ nand->dev_ready = NULL;
+ } else {
+ nand->dev_ready = sunxi_nfc_dev_ready;
+ if (sel->rb.type == RB_NATIVE)
+ ctl |= (sel->rb.info.nativeid << 3);
+ }
+
+ writel(mtd->writesize, nfc->regs + NFC_REG_SPARE_AREA);
+
+ if (nfc->clk_rate != sunxi_nand->clk_rate) {
+ clk_set_rate(nfc->sclk, sunxi_nand->clk_rate);
+ nfc->clk_rate = sunxi_nand->clk_rate;
+ }
+ }
+
+ writel(ctl, nfc->regs + NFC_REG_CTL);
+
+ sunxi_nand->selected = chip;
+}
+
+static void sunxi_nfc_read_buf(struct mtd_info *mtd, uint8_t *buf, int len)
+{
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(mtd);
+ struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller);
+ int cnt;
+ int offs = 0;
+ u32 tmp;
+
+ while (len > offs) {
+ cnt = len - offs;
+ if (cnt > 1024)
+ cnt = 1024;
+
+ while ((readl(nfc->regs + NFC_REG_ST) & NFC_CMD_FIFO_STATUS))
+ ;
+ writel(cnt, nfc->regs + NFC_REG_CNT);
+ tmp = NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD;
+ writel(tmp, nfc->regs + NFC_REG_CMD);
+ sunxi_nfc_wait_int(nfc, NFC_CMD_INT_FLAG, 0);
+ if (buf)
+ memcpy_fromio(buf + offs, nfc->regs + NFC_RAM0_BASE,
+ cnt);
+ offs += cnt;
+ }
+}
+
+static void sunxi_nfc_write_buf(struct mtd_info *mtd, const uint8_t *buf,
+ int len)
+{
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(mtd);
+ struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller);
+ int cnt;
+ int offs = 0;
+ u32 tmp;
+
+ while (len > offs) {
+ cnt = len - offs;
+ if (cnt > 1024)
+ cnt = 1024;
+
+ while ((readl(nfc->regs + NFC_REG_ST) & NFC_CMD_FIFO_STATUS))
+ ;
+ writel(cnt, nfc->regs + NFC_REG_CNT);
+ memcpy_toio(nfc->regs + NFC_RAM0_BASE, buf + offs, cnt);
+ tmp = NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD |
+ NFC_ACCESS_DIR;
+ writel(tmp, nfc->regs + NFC_REG_CMD);
+ sunxi_nfc_wait_int(nfc, NFC_CMD_INT_FLAG, 0);
+ offs += cnt;
+ }
+}
+
+static uint8_t sunxi_nfc_read_byte(struct mtd_info *mtd)
+{
+ uint8_t ret;
+
+ sunxi_nfc_read_buf(mtd, &ret, 1);
+
+ return ret;
+}
+
+static void sunxi_nfc_cmd_ctrl(struct mtd_info *mtd, int dat,
+ unsigned int ctrl)
+{
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(mtd);
+ struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller);
+ u32 tmp;
+
+ while ((readl(nfc->regs + NFC_REG_ST) & NFC_CMD_FIFO_STATUS))
+ ;
+
+ if (ctrl & NAND_CTRL_CHANGE) {
+ tmp = readl(nfc->regs + NFC_REG_CTL);
+ if (ctrl & NAND_NCE)
+ tmp |= NFC_CE_CTL;
+ else
+ tmp &= ~NFC_CE_CTL;
+ writel(tmp, nfc->regs + NFC_REG_CTL);
+ }
+
+ if (dat == NAND_CMD_NONE)
+ return;
+
+ if (ctrl & NAND_CLE) {
+ writel(NFC_SEND_CMD1 | dat, nfc->regs + NFC_REG_CMD);
+ } else {
+ writel(dat, nfc->regs + NFC_REG_ADDR_LOW);
+ writel(NFC_SEND_ADR, nfc->regs + NFC_REG_CMD);
+ }
+
+ sunxi_nfc_wait_int(nfc, NFC_CMD_INT_FLAG, 0);
+}
+
+static int sunxi_nand_chip_set_timings(struct sunxi_nand_chip *chip,
+ const struct nand_sdr_timings *timings)
+{
+ u32 min_clk_period = 0;
+
+ /* T1 <=> tCLS */
+ if (timings->tCLS_min > min_clk_period)
+ min_clk_period = timings->tCLS_min;
+
+ /* T2 <=> tCLH */
+ if (timings->tCLH_min > min_clk_period)
+ min_clk_period = timings->tCLH_min;
+
+ /* T3 <=> tCS */
+ if (timings->tCS_min > min_clk_period)
+ min_clk_period = timings->tCS_min;
+
+ /* T4 <=> tCH */
+ if (timings->tCH_min > min_clk_period)
+ min_clk_period = timings->tCH_min;
+
+ /* T5 <=> tWP */
+ if (timings->tWP_min > min_clk_period)
+ min_clk_period = timings->tWP_min;
+
+ /* T6 <=> tWH */
+ if (timings->tWH_min > min_clk_period)
+ min_clk_period = timings->tWH_min;
+
+ /* T7 <=> tALS */
+ if (timings->tALS_min > min_clk_period)
+ min_clk_period = timings->tALS_min;
+
+ /* T8 <=> tDS */
+ if (timings->tDS_min > min_clk_period)
+ min_clk_period = timings->tDS_min;
+
+ /* T9 <=> tDH */
+ if (timings->tDH_min > min_clk_period)
+ min_clk_period = timings->tDH_min;
+
+ /* T10 <=> tRR */
+ if (timings->tRR_min > (min_clk_period * 3))
+ min_clk_period = (timings->tRR_min + 2) / 3;
+
+ /* T11 <=> tALH */
+ if (timings->tALH_min > min_clk_period)
+ min_clk_period = timings->tALH_min;
+
+ /* T12 <=> tRP */
+ if (timings->tRP_min > min_clk_period)
+ min_clk_period = timings->tRP_min;
+
+ /* T13 <=> tREH */
+ if (timings->tREH_min > min_clk_period)
+ min_clk_period = timings->tREH_min;
+
+ /* T14 <=> tRC */
+ if (timings->tRC_min > (min_clk_period * 2))
+ min_clk_period = (timings->tRC_min + 1) / 2;
+
+ /* T15 <=> tWC */
+ if (timings->tWC_min > (min_clk_period * 2))
+ min_clk_period = (timings->tWC_min + 1) / 2;
+
+
+ /* min_clk_period = (NAND-clk-period * 2) */
+ if (min_clk_period < 1000)
+ min_clk_period = 1000;
+
+ min_clk_period /= 1000;
+ chip->clk_rate = (2 * 1000000000) / min_clk_period;
+
+ /* TODO: configure T16-T19 */
+
+ return 0;
+}
+
+static int sunxi_nand_chip_init_timings(struct sunxi_nand_chip *chip,
+ struct device_node *np)
+{
+ const struct nand_sdr_timings *timings;
+ int ret;
+
+ ret = onfi_get_async_timing_mode(&chip->nand);
+ if (ret == ONFI_TIMING_MODE_UNKNOWN) {
+ ret = of_get_nand_onfi_timing_mode(np);
+ if (ret < 0)
+ return ret;
+ }
+
+ ret = fls(ret);
+ if (!ret)
+ return -EINVAL;
+
+ timings = onfi_async_timing_mode_to_sdr_timings(ret - 1);
+ if (IS_ERR(timings))
+ return PTR_ERR(timings);
+
+ return sunxi_nand_chip_set_timings(chip, timings);
+}
+
+static int sunxi_nand_chip_init(struct device *dev, struct sunxi_nfc *nfc,
+ struct device_node *np)
+{
+ const struct nand_sdr_timings *timings;
+ struct sunxi_nand_chip *chip;
+ struct mtd_part_parser_data ppdata;
+ struct mtd_info *mtd;
+ struct nand_chip *nand;
+ u32 strength;
+ u32 blk_size;
+ int nsels;
+ int ret;
+ int i;
+ u32 tmp;
+
+ if (!of_get_property(np, "reg", &nsels))
+ return -EINVAL;
+
+ nsels /= sizeof(u32);
+ if (!nsels)
+ return -EINVAL;
+
+ chip = devm_kzalloc(dev,
+ sizeof(*chip) +
+ (nsels * sizeof(struct sunxi_nand_chip_sel)),
+ GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->nsels = nsels;
+ chip->selected = -1;
+
+ for (i = 0; i < nsels; i++) {
+ ret = of_property_read_u32_index(np, "reg", i, &tmp);
+ if (ret)
+ return ret;
+
+ if (tmp > 7)
+ return -EINVAL;
+
+ if (test_and_set_bit(tmp, &nfc->assigned_cs))
+ return -EINVAL;
+
+ chip->sels[i].cs = tmp;
+
+ if (!of_property_read_u32_index(np, "allwinner,rb", i, &tmp) &&
+ tmp < 2) {
+ chip->sels[i].rb.type = RB_NATIVE;
+ chip->sels[i].rb.info.nativeid = tmp;
+ } else {
+ ret = of_get_named_gpio(np, "rb-gpios", i);
+ if (ret >= 0) {
+ tmp = ret;
+ chip->sels[i].rb.type = RB_GPIO;
+ chip->sels[i].rb.info.gpio = tmp;
+ ret = devm_gpio_request(dev, tmp, "nand-rb");
+ if (ret)
+ return ret;
+
+ ret = gpio_direction_input(tmp);
+ if (ret)
+ return ret;
+ } else {
+ chip->sels[i].rb.type = RB_NONE;
+ }
+ }
+ }
+
+ timings = onfi_async_timing_mode_to_sdr_timings(0);
+ if (IS_ERR(timings))
+ return PTR_ERR(timings);
+
+ ret = sunxi_nand_chip_set_timings(chip, timings);
+
+ nand = &chip->nand;
+ nand->controller = &nfc->controller;
+ nand->select_chip = sunxi_nfc_select_chip;
+ nand->cmd_ctrl = sunxi_nfc_cmd_ctrl;
+ nand->read_buf = sunxi_nfc_read_buf;
+ nand->write_buf = sunxi_nfc_write_buf;
+ nand->read_byte = sunxi_nfc_read_byte;
+
+ nand->ecc.mode = of_get_nand_ecc_mode(np);
+ if (of_get_nand_on_flash_bbt(np))
+ nand->bbt_options |= NAND_BBT_USE_FLASH;
+
+ mtd = &chip->mtd;
+ mtd->priv = nand;
+ mtd->owner = THIS_MODULE;
+
+ ret = nand_scan_ident(mtd, nsels, NULL);
+ if (ret)
+ return ret;
+
+ ret = sunxi_nand_chip_init_timings(chip, np);
+ if (ret)
+ return ret;
+
+ if (nand->ecc.mode == NAND_ECC_SOFT_BCH) {
+ if (!of_get_nand_ecc_level(np, &strength, &blk_size)) {
+ nand->ecc_step_ds = blk_size;
+ nand->ecc_strength_ds = strength;
+ }
+
+ nand->ecc.size = nand->ecc_step_ds;
+ nand->ecc.bytes = (((nand->ecc_strength_ds *
+ fls(8 * nand->ecc_step_ds)) + 7) / 8);
+ }
+
+ ret = nand_scan_tail(mtd);
+ if (ret)
+ return ret;
+
+ if (of_property_read_string(np, "nand-name", &mtd->name)) {
+ snprintf(chip->default_name, MAX_NAME_SIZE,
+ DEFAULT_NAME_FORMAT, chip->sels[i].cs);
+ mtd->name = chip->default_name;
+ }
+
+ ppdata.of_node = np;
+ ret = mtd_device_parse_register(mtd, NULL, &ppdata, NULL, 0);
+ if (!ret)
+ return ret;
+
+ list_add_tail(&chip->node, &nfc->chips);
+
+ return 0;
+}
+
+static int sunxi_nand_chips_init(struct device *dev, struct sunxi_nfc *nfc)
+{
+ struct device_node *np = dev->of_node;
+ struct device_node *nand_np;
+ int nchips = of_get_child_count(np);
+ int ret;
+
+ if (nchips > 8)
+ return -EINVAL;
+
+ for_each_child_of_node(np, nand_np) {
+ ret = sunxi_nand_chip_init(dev, nfc, nand_np);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int sunxi_nfc_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct resource *r;
+ struct sunxi_nfc *nfc;
+ int ret;
+
+ nfc = devm_kzalloc(dev, sizeof(*nfc), GFP_KERNEL);
+ if (!nfc) {
+ dev_err(dev, "failed to allocate NFC struct\n");
+ return -ENOMEM;
+ }
+
+ spin_lock_init(&nfc->controller.lock);
+ init_waitqueue_head(&nfc->controller.wq);
+ INIT_LIST_HEAD(&nfc->chips);
+
+ r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ nfc->regs = devm_ioremap_resource(dev, r);
+ if (IS_ERR(nfc->regs)) {
+ dev_err(dev, "failed to remap iomem\n");
+ return PTR_ERR(nfc->regs);
+ }
+
+ nfc->irq = platform_get_irq(pdev, 0);
+ if (nfc->irq < 0) {
+ dev_err(dev, "failed to retrieve irq\n");
+ return nfc->irq;
+ }
+
+ nfc->ahb_clk = devm_clk_get(dev, "ahb_clk");
+ if (IS_ERR(nfc->ahb_clk)) {
+ dev_err(dev, "failed to retrieve ahb_clk\n");
+ return PTR_ERR(nfc->ahb_clk);
+ }
+
+ ret = clk_prepare_enable(nfc->ahb_clk);
+ if (ret)
+ return ret;
+
+ nfc->sclk = devm_clk_get(dev, "sclk");
+ if (IS_ERR(nfc->sclk)) {
+ dev_err(dev, "failed to retrieve nand_clk\n");
+ ret = PTR_ERR(nfc->sclk);
+ goto out_ahb_clk_unprepare;
+ }
+
+ ret = clk_prepare_enable(nfc->sclk);
+ if (ret)
+ goto out_ahb_clk_unprepare;
+
+ /* Reset NFC */
+ writel(readl(nfc->regs + NFC_REG_CTL) | NFC_RESET,
+ nfc->regs + NFC_REG_CTL);
+ while (readl(nfc->regs + NFC_REG_CTL) & NFC_RESET)
+ ;
+
+ writel(0, nfc->regs + NFC_REG_INT);
+ ret = devm_request_irq(dev, nfc->irq, sunxi_nfc_interrupt,
+ 0, "sunxi-nand", nfc);
+ if (ret)
+ goto out_sclk_unprepare;
+
+ platform_set_drvdata(pdev, nfc);
+
+ writel(0x100, nfc->regs + NFC_REG_TIMING_CTL);
+ writel(0x7ff, nfc->regs + NFC_REG_TIMING_CFG);
+
+ ret = sunxi_nand_chips_init(dev, nfc);
+ if (ret) {
+ dev_err(dev, "failed to init nand chips\n");
+ goto out_sclk_unprepare;
+ }
+
+ return 0;
+
+out_sclk_unprepare:
+ clk_disable_unprepare(nfc->sclk);
+out_ahb_clk_unprepare:
+ clk_disable_unprepare(nfc->ahb_clk);
+
+ return ret;
+}
+
+static const struct of_device_id sunxi_nfc_ids[] = {
+ { .compatible = "allwinner,sun4i-nand" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, sunxi_nfc_ids);
+
+static struct platform_driver sunxi_nfc_driver = {
+ .driver = {
+ .name = "sunxi_nand",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(sunxi_nfc_ids),
+ },
+ .probe = sunxi_nfc_probe,
+};
+module_platform_driver(sunxi_nfc_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Boris BREZILLON");
+MODULE_DESCRIPTION("Allwinner NAND Flash Controller driver");
+MODULE_ALIAS("platform:sunxi_nfc");
--
1.7.9.5
Add HW ECC support for the sunxi NAND Flash Controller.
Signed-off-by: Boris BREZILLON <[email protected]>
---
drivers/mtd/nand/sunxi_nand.c | 279 +++++++++++++++++++++++++++++++++++++++--
1 file changed, 266 insertions(+), 13 deletions(-)
diff --git a/drivers/mtd/nand/sunxi_nand.c b/drivers/mtd/nand/sunxi_nand.c
index 1014b2a..b90268f 100644
--- a/drivers/mtd/nand/sunxi_nand.c
+++ b/drivers/mtd/nand/sunxi_nand.c
@@ -163,6 +163,11 @@ struct sunxi_nand_chip_sel {
#define DEFAULT_NAME_FORMAT "nand@%d"
#define MAX_NAME_SIZE (sizeof("nand@") + 2)
+struct sunxi_nand_hw_ecc {
+ int mode;
+ struct nand_ecclayout layout;
+};
+
struct sunxi_nand_chip {
struct list_head node;
struct nand_chip nand;
@@ -402,6 +407,126 @@ static void sunxi_nfc_cmd_ctrl(struct mtd_info *mtd, int dat,
sunxi_nfc_wait_int(nfc, NFC_CMD_INT_FLAG, 0);
}
+static int sunxi_nfc_hwecc_read_page(struct mtd_info *mtd,
+ struct nand_chip *chip, uint8_t *buf,
+ int oob_required, int page)
+{
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(mtd);
+ struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller);
+ struct nand_ecc_ctrl *ecc = &chip->ecc;
+ struct nand_ecclayout *layout = ecc->layout;
+ struct sunxi_nand_hw_ecc *data = ecc->priv;
+ unsigned int max_bitflips = 0;
+ int offset;
+ u32 tmp;
+ int i;
+
+ tmp = readl(nfc->regs + NFC_REG_ECC_CTL);
+ tmp &= ~(NFC_ECC_MODE | NFC_ECC_PIPELINE | NFC_ECC_BLOCK_SIZE |
+ NFC_ECC_BLOCK_SIZE);
+ tmp |= NFC_ECC_EN | (data->mode << NFC_ECC_MODE_SHIFT);
+ writel(tmp, nfc->regs + NFC_REG_ECC_CTL);
+
+ for (i = 0; i < mtd->writesize / ecc->size; i++) {
+ if (i)
+ chip->cmdfunc(mtd, NAND_CMD_RNDOUT, i * ecc->size, -1);
+ chip->read_buf(mtd, NULL, chip->ecc.size);
+ offset = mtd->writesize + layout->eccpos[i * ecc->bytes] - 4;
+ chip->cmdfunc(mtd, NAND_CMD_RNDOUT, offset, -1);
+ while ((readl(nfc->regs + NFC_REG_ST) & NFC_CMD_FIFO_STATUS))
+ ;
+ tmp = NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD | (1 << 30);
+ writel(tmp, nfc->regs + NFC_REG_CMD);
+ sunxi_nfc_wait_int(nfc, NFC_CMD_INT_FLAG, 0);
+ memcpy_fromio(buf + (i * ecc->size), nfc->regs + NFC_RAM0_BASE,
+ chip->ecc.size);
+
+ if (readl(nfc->regs + NFC_REG_ECC_ST) & 0x1) {
+ mtd->ecc_stats.failed++;
+ } else {
+ tmp = readl(nfc->regs + NFC_REG_ECC_CNT0) & 0xff;
+ mtd->ecc_stats.corrected += tmp;
+ max_bitflips = max_t(unsigned int, max_bitflips, tmp);
+ }
+ }
+
+ if (oob_required) {
+ chip->cmdfunc(mtd, NAND_CMD_RNDOUT, mtd->writesize, -1);
+ chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
+ }
+
+ tmp = readl(nfc->regs + NFC_REG_ECC_CTL);
+ tmp &= ~NFC_ECC_EN;
+
+ writel(tmp, nfc->regs + NFC_REG_ECC_CTL);
+
+ return max_bitflips;
+}
+
+static int sunxi_nfc_hwecc_write_page(struct mtd_info *mtd,
+ struct nand_chip *chip,
+ const uint8_t *buf,
+ int oob_required)
+{
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(mtd);
+ struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller);
+ struct nand_ecc_ctrl *ecc = &chip->ecc;
+ struct nand_ecclayout *layout = ecc->layout;
+ struct sunxi_nand_hw_ecc *data = ecc->priv;
+ int offset;
+ u32 tmp;
+ int i;
+ int j;
+
+ tmp = readl(nfc->regs + NFC_REG_ECC_CTL);
+ tmp &= ~(NFC_ECC_MODE | NFC_ECC_PIPELINE | NFC_ECC_BLOCK_SIZE |
+ NFC_ECC_BLOCK_SIZE);
+ tmp |= NFC_ECC_EN | (data->mode << NFC_ECC_MODE_SHIFT);
+
+ writel(tmp, nfc->regs + NFC_REG_ECC_CTL);
+
+ for (i = 0; i < mtd->writesize / ecc->size; i++) {
+ if (i)
+ chip->cmdfunc(mtd, NAND_CMD_RNDIN, i * ecc->size, -1);
+
+ chip->write_buf(mtd, buf + (i * ecc->size), ecc->size);
+ offset = mtd->writesize + layout->eccpos[i * ecc->bytes] - 4;
+ chip->cmdfunc(mtd, NAND_CMD_RNDIN, offset, -1);
+ while ((readl(nfc->regs + NFC_REG_ST) & NFC_CMD_FIFO_STATUS))
+ ;
+
+ /* Fill OOB data in */
+ for (j = 0; j < 4; j++) {
+ if (oob_required) {
+ offset = layout->eccpos[i * ecc->size] - 4;
+ writeb(chip->oob_poi[offset + j],
+ nfc->regs + NFC_REG_USER_DATA_BASE + j);
+ } else {
+ writeb(0xff,
+ nfc->regs + NFC_REG_USER_DATA_BASE + j);
+ }
+ }
+
+ tmp = NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD |
+ NFC_ACCESS_DIR | (1 << 30);
+ writel(tmp, nfc->regs + NFC_REG_CMD);
+ sunxi_nfc_wait_int(nfc, NFC_CMD_INT_FLAG, 0);
+ }
+
+ if (oob_required && chip->ecc.layout->oobfree[0].length > 2) {
+ chip->cmdfunc(mtd, NAND_CMD_RNDIN, mtd->writesize, -1);
+ chip->write_buf(mtd, chip->oob_poi,
+ chip->ecc.layout->oobfree[0].length - 2);
+ }
+
+ tmp = readl(nfc->regs + NFC_REG_ECC_CTL);
+ tmp &= ~(NFC_ECC_EN | NFC_ECC_PIPELINE);
+
+ writel(tmp, nfc->regs + NFC_REG_ECC_CTL);
+
+ return 0;
+}
+
static int sunxi_nand_chip_set_timings(struct sunxi_nand_chip *chip,
const struct nand_sdr_timings *timings)
{
@@ -504,6 +629,144 @@ static int sunxi_nand_chip_init_timings(struct sunxi_nand_chip *chip,
return sunxi_nand_chip_set_timings(chip, timings);
}
+static int sunxi_nand_chip_hwecc_init(struct device *dev,
+ struct sunxi_nand_chip *chip,
+ struct mtd_info *mtd,
+ struct device_node *np)
+{
+ struct nand_chip *nand = &chip->nand;
+ struct nand_ecc_ctrl *ecc = &nand->ecc;
+ struct sunxi_nand_hw_ecc *data;
+ struct nand_ecclayout *layout;
+ int nsectors;
+ int i;
+ int j;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ ecc->read_page = sunxi_nfc_hwecc_read_page;
+ ecc->write_page = sunxi_nfc_hwecc_write_page;
+
+ if (nand->ecc_strength_ds <= 16) {
+ nand->ecc_strength_ds = 16;
+ data->mode = 0;
+ } else if (nand->ecc_strength_ds <= 24) {
+ nand->ecc_strength_ds = 24;
+ data->mode = 1;
+ } else if (nand->ecc_strength_ds <= 28) {
+ nand->ecc_strength_ds = 28;
+ data->mode = 2;
+ } else if (nand->ecc_strength_ds <= 32) {
+ nand->ecc_strength_ds = 32;
+ data->mode = 3;
+ } else if (nand->ecc_strength_ds <= 40) {
+ nand->ecc_strength_ds = 40;
+ data->mode = 4;
+ } else if (nand->ecc_strength_ds <= 48) {
+ nand->ecc_strength_ds = 48;
+ data->mode = 5;
+ } else if (nand->ecc_strength_ds <= 56) {
+ nand->ecc_strength_ds = 56;
+ data->mode = 6;
+ } else if (nand->ecc_strength_ds <= 60) {
+ nand->ecc_strength_ds = 60;
+ data->mode = 7;
+ } else if (nand->ecc_strength_ds <= 64) {
+ nand->ecc_strength_ds = 64;
+ data->mode = 8;
+ } else {
+ dev_err(dev, "unsupported strength\n");
+ return -ENOTSUPP;
+ }
+
+ /* HW ECC always request ECC bytes for 1024 bytes blocks */
+ ecc->bytes = ((nand->ecc_strength_ds * fls(8 * 1024)) + 7) / 8;
+
+ /* HW ECC always work with even numbers of ECC bytes */
+ if (ecc->bytes % 2)
+ ecc->bytes++;
+ ecc->strength = nand->ecc_strength_ds;
+ ecc->size = nand->ecc_step_ds;
+
+ layout = &data->layout;
+ nsectors = mtd->writesize / ecc->size;
+
+ if (mtd->oobsize < ((ecc->bytes + 4) * nsectors))
+ return -EINVAL;
+
+ layout->eccbytes = (ecc->bytes * nsectors);
+
+ /*
+ * The first 2 bytes are used for BB markers.
+ * We merge the 4 user available bytes from HW ECC with this
+ * first section, hence why the + 2 operation (- 2 + 4).
+ */
+ layout->oobfree[0].length = mtd->oobsize + 2 -
+ ((ecc->bytes + 4) * nsectors);
+ layout->oobfree[0].offset = 2;
+ for (i = 0; i < nsectors; i++) {
+ /*
+ * The first 4 ECC block bytes are already counted in the first
+ * obbfree entry.
+ */
+ if (i) {
+ layout->oobfree[i].offset =
+ layout->oobfree[i - 1].offset +
+ layout->oobfree[i - 1].length +
+ ecc->bytes;
+ layout->oobfree[i].length = 4;
+ }
+
+ for (j = 0; j < ecc->bytes; j++)
+ layout->eccpos[(ecc->bytes * i) + j] =
+ layout->oobfree[i].offset +
+ layout->oobfree[i].length + j;
+ }
+
+ ecc->layout = layout;
+ ecc->priv = data;
+
+ return 0;
+}
+
+static int sunxi_nand_chip_ecc_init(struct device *dev,
+ struct sunxi_nand_chip *chip,
+ struct mtd_info *mtd,
+ struct device_node *np)
+{
+ struct nand_chip *nand = &chip->nand;
+ u32 strength;
+ u32 blk_size;
+ int ret;
+
+ nand->ecc.mode = of_get_nand_ecc_mode(np);
+
+ if (!of_get_nand_ecc_level(np, &strength, &blk_size)) {
+ nand->ecc_step_ds = blk_size;
+ nand->ecc_strength_ds = strength;
+ }
+
+ switch (nand->ecc.mode) {
+ case NAND_ECC_SOFT_BCH:
+ nand->ecc.size = nand->ecc_step_ds;
+ nand->ecc.bytes = ((nand->ecc_strength_ds *
+ fls(8 * nand->ecc_step_ds)) + 7) / 8;
+ break;
+ case NAND_ECC_HW:
+ ret = sunxi_nand_chip_hwecc_init(dev, chip, mtd, np);
+ if (ret)
+ return ret;
+ break;
+ case NAND_ECC_NONE:
+ default:
+ break;
+ }
+
+ return 0;
+}
+
static int sunxi_nand_chip_init(struct device *dev, struct sunxi_nfc *nfc,
struct device_node *np)
{
@@ -512,8 +775,6 @@ static int sunxi_nand_chip_init(struct device *dev, struct sunxi_nfc *nfc,
struct mtd_part_parser_data ppdata;
struct mtd_info *mtd;
struct nand_chip *nand;
- u32 strength;
- u32 blk_size;
int nsels;
int ret;
int i;
@@ -586,7 +847,6 @@ static int sunxi_nand_chip_init(struct device *dev, struct sunxi_nfc *nfc,
nand->write_buf = sunxi_nfc_write_buf;
nand->read_byte = sunxi_nfc_read_byte;
- nand->ecc.mode = of_get_nand_ecc_mode(np);
if (of_get_nand_on_flash_bbt(np))
nand->bbt_options |= NAND_BBT_USE_FLASH;
@@ -602,16 +862,9 @@ static int sunxi_nand_chip_init(struct device *dev, struct sunxi_nfc *nfc,
if (ret)
return ret;
- if (nand->ecc.mode == NAND_ECC_SOFT_BCH) {
- if (!of_get_nand_ecc_level(np, &strength, &blk_size)) {
- nand->ecc_step_ds = blk_size;
- nand->ecc_strength_ds = strength;
- }
-
- nand->ecc.size = nand->ecc_step_ds;
- nand->ecc.bytes = (((nand->ecc_strength_ds *
- fls(8 * nand->ecc_step_ds)) + 7) / 8);
- }
+ ret = sunxi_nand_chip_ecc_init(dev, chip, mtd, np);
+ if (ret)
+ return ret;
ret = nand_scan_tail(mtd);
if (ret)
--
1.7.9.5
Add documentation for the ONFI NAND timing mode property.
Signed-off-by: Boris BREZILLON <[email protected]>
---
Changes since v2:
- fix description of the nand-timing-mode property: the mode property is
a mask containing all supported modes, each mode is encoded as a bit
position
Documentation/devicetree/bindings/mtd/nand.txt | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/Documentation/devicetree/bindings/mtd/nand.txt b/Documentation/devicetree/bindings/mtd/nand.txt
index 0c962296..60c7112 100644
--- a/Documentation/devicetree/bindings/mtd/nand.txt
+++ b/Documentation/devicetree/bindings/mtd/nand.txt
@@ -8,3 +8,10 @@
E.g. : nand-ecc-level = <4 512>; /* 4 bits / 512 bytes */
- nand-bus-width : 8 or 16 bus width if not present 8
- nand-on-flash-bbt: boolean to enable on flash bbt option if not present false
+- onfi,nand-timing-mode: an integer encoding the supported ONFI timing modes of
+ the NAND chip. Each supported mode is represented as a bit position (i.e. :
+ mode 0 and 1 => (1 << 0) | (1 << 1) = 0x3).
+ This is only used when the chip does not support the ONFI standard.
+ The last bit set represent the closest mode fulfilling the NAND chip timings.
+ For a full description of the different timing modes see this document:
+ http://www.onfi.org/~/media/ONFI/specs/onfi_3_1_spec.pdf
--
1.7.9.5
Boris,
Can you please explain to me why you mail all your patches _To:_ me?
As in, why do I appear in the To: line of all the patches you seem to
mail out, whether or not they're relevant to me. I see this very
regularly from you - virtually all patches I see on the LAKML mailing
list from you are always sent To: me as well.
Take for instance this one. It doesn't match up with anything in
MAINTAINERS for me. It doesn't even touch a file that I've touched.
Yet somehow you think that I should be in the To: header.
Being in the To: header means that you expect the recipient to do
something with your email. The Cc: header is to circulate copies of
your email to people who may be interested.
I'm neither for this stuff. Please stop this.
Thanks.
On Thu, Jan 30, 2014 at 02:39:36PM +0100, Boris BREZILLON wrote:
> Add support for the sunxi NAND Flash Controller (NFC).
>
> Signed-off-by: Boris BREZILLON <[email protected]>
> ---
> Hello,
>
> This version fixes a bug in the R/B GPIO config block.
> The timing config order is now respected, but I'll wait for Jason work
> regarding timing config in NAND core code before posting the 3rd version
> of this series.
>
> Best Regards,
>
> Boris
>
> Changes since v2:
> - fix R/B GPIO retrieval/config bug
> - fix timings configuration order (set mode 0 -> scan -> set best supported
> mode)
>
> drivers/mtd/nand/Kconfig | 6 +
> drivers/mtd/nand/Makefile | 1 +
> drivers/mtd/nand/sunxi_nand.c | 758 +++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 765 insertions(+)
> create mode 100644 drivers/mtd/nand/sunxi_nand.c
>
> diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig
> index 93ae6a6..784dd42 100644
> --- a/drivers/mtd/nand/Kconfig
> +++ b/drivers/mtd/nand/Kconfig
> @@ -510,4 +510,10 @@ config MTD_NAND_XWAY
> Enables support for NAND Flash chips on Lantiq XWAY SoCs. NAND is attached
> to the External Bus Unit (EBU).
>
> +config MTD_NAND_SUNXI
> + tristate "Support for NAND on Allwinner SoCs"
> + depends on ARCH_SUNXI
> + help
> + Enables support for NAND Flash chips on Allwinner SoCs.
> +
> endif # MTD_NAND
> diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile
> index bbea7a6..e3b4a34 100644
> --- a/drivers/mtd/nand/Makefile
> +++ b/drivers/mtd/nand/Makefile
> @@ -49,5 +49,6 @@ obj-$(CONFIG_MTD_NAND_JZ4740) += jz4740_nand.o
> obj-$(CONFIG_MTD_NAND_GPMI_NAND) += gpmi-nand/
> obj-$(CONFIG_MTD_NAND_XWAY) += xway_nand.o
> obj-$(CONFIG_MTD_NAND_BCM47XXNFLASH) += bcm47xxnflash/
> +obj-$(CONFIG_MTD_NAND_SUNXI) += sunxi_nand.o
>
> nand-objs := nand_base.o nand_bbt.o
> diff --git a/drivers/mtd/nand/sunxi_nand.c b/drivers/mtd/nand/sunxi_nand.c
> new file mode 100644
> index 0000000..1014b2a
> --- /dev/null
> +++ b/drivers/mtd/nand/sunxi_nand.c
> @@ -0,0 +1,758 @@
> +/*
> + * Copyright (C) 2013 Boris BREZILLON <[email protected]>
> + *
> + * Derived from:
> + * https://github.com/yuq/sunxi-nfc-mtd
> + * Copyright (C) 2013 Qiang Yu <[email protected]>
> + *
> + * https://github.com/hno/Allwinner-Info
> + * Copyright (C) 2013 Henrik Nordstr?m <Henrik Nordstr?m>
> + *
> + * Copyright (C) 2013 Dmitriy B. <[email protected]>
> + * Copyright (C) 2013 Sergey Lapin <[email protected]>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + */
> +
> +#include <linux/dma-mapping.h>
> +#include <linux/slab.h>
> +#include <linux/module.h>
> +#include <linux/moduleparam.h>
> +#include <linux/platform_device.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/of_gpio.h>
> +#include <linux/of_mtd.h>
> +#include <linux/mtd/mtd.h>
> +#include <linux/mtd/nand.h>
> +#include <linux/mtd/partitions.h>
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/dmaengine.h>
> +#include <linux/gpio.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +
> +#define NFC_REG_CTL 0x0000
> +#define NFC_REG_ST 0x0004
> +#define NFC_REG_INT 0x0008
> +#define NFC_REG_TIMING_CTL 0x000C
> +#define NFC_REG_TIMING_CFG 0x0010
> +#define NFC_REG_ADDR_LOW 0x0014
> +#define NFC_REG_ADDR_HIGH 0x0018
> +#define NFC_REG_SECTOR_NUM 0x001C
> +#define NFC_REG_CNT 0x0020
> +#define NFC_REG_CMD 0x0024
> +#define NFC_REG_RCMD_SET 0x0028
> +#define NFC_REG_WCMD_SET 0x002C
> +#define NFC_REG_IO_DATA 0x0030
> +#define NFC_REG_ECC_CTL 0x0034
> +#define NFC_REG_ECC_ST 0x0038
> +#define NFC_REG_DEBUG 0x003C
> +#define NFC_REG_ECC_CNT0 0x0040
> +#define NFC_REG_ECC_CNT1 0x0044
> +#define NFC_REG_ECC_CNT2 0x0048
> +#define NFC_REG_ECC_CNT3 0x004c
> +#define NFC_REG_USER_DATA_BASE 0x0050
> +#define NFC_REG_SPARE_AREA 0x00A0
> +#define NFC_RAM0_BASE 0x0400
> +#define NFC_RAM1_BASE 0x0800
> +
> +/*define bit use in NFC_CTL*/
> +#define NFC_EN (1 << 0)
> +#define NFC_RESET (1 << 1)
> +#define NFC_BUS_WIDYH (1 << 2)
> +#define NFC_RB_SEL (1 << 3)
> +#define NFC_CE_SEL (7 << 24)
> +#define NFC_CE_CTL (1 << 6)
> +#define NFC_CE_CTL1 (1 << 7)
> +#define NFC_PAGE_SIZE (0xf << 8)
> +#define NFC_SAM (1 << 12)
> +#define NFC_RAM_METHOD (1 << 14)
> +#define NFC_DEBUG_CTL (1 << 31)
> +
> +/*define bit use in NFC_ST*/
> +#define NFC_RB_B2R (1 << 0)
> +#define NFC_CMD_INT_FLAG (1 << 1)
> +#define NFC_DMA_INT_FLAG (1 << 2)
> +#define NFC_CMD_FIFO_STATUS (1 << 3)
> +#define NFC_STA (1 << 4)
> +#define NFC_NATCH_INT_FLAG (1 << 5)
> +#define NFC_RB_STATE0 (1 << 8)
> +#define NFC_RB_STATE1 (1 << 9)
> +#define NFC_RB_STATE2 (1 << 10)
> +#define NFC_RB_STATE3 (1 << 11)
> +
> +/*define bit use in NFC_INT*/
> +#define NFC_B2R_INT_ENABLE (1 << 0)
> +#define NFC_CMD_INT_ENABLE (1 << 1)
> +#define NFC_DMA_INT_ENABLE (1 << 2)
> +#define NFC_INT_MASK (NFC_B2R_INT_ENABLE | \
> + NFC_CMD_INT_ENABLE | \
> + NFC_DMA_INT_ENABLE)
> +
> +
> +/*define bit use in NFC_CMD*/
> +#define NFC_CMD_LOW_BYTE (0xff << 0)
> +#define NFC_CMD_HIGH_BYTE (0xff << 8)
> +#define NFC_ADR_NUM (0x7 << 16)
> +#define NFC_SEND_ADR (1 << 19)
> +#define NFC_ACCESS_DIR (1 << 20)
> +#define NFC_DATA_TRANS (1 << 21)
> +#define NFC_SEND_CMD1 (1 << 22)
> +#define NFC_WAIT_FLAG (1 << 23)
> +#define NFC_SEND_CMD2 (1 << 24)
> +#define NFC_SEQ (1 << 25)
> +#define NFC_DATA_SWAP_METHOD (1 << 26)
> +#define NFC_ROW_AUTO_INC (1 << 27)
> +#define NFC_SEND_CMD3 (1 << 28)
> +#define NFC_SEND_CMD4 (1 << 29)
> +#define NFC_CMD_TYPE (3 << 30)
> +
> +/* define bit use in NFC_RCMD_SET*/
> +#define NFC_READ_CMD (0xff << 0)
> +#define NFC_RANDOM_READ_CMD0 (0xff << 8)
> +#define NFC_RANDOM_READ_CMD1 (0xff << 16)
> +
> +/*define bit use in NFC_WCMD_SET*/
> +#define NFC_PROGRAM_CMD (0xff << 0)
> +#define NFC_RANDOM_WRITE_CMD (0xff << 8)
> +#define NFC_READ_CMD0 (0xff << 16)
> +#define NFC_READ_CMD1 (0xff << 24)
> +
> +/*define bit use in NFC_ECC_CTL*/
> +#define NFC_ECC_EN (1 << 0)
> +#define NFC_ECC_PIPELINE (1 << 3)
> +#define NFC_ECC_EXCEPTION (1 << 4)
> +#define NFC_ECC_BLOCK_SIZE (1 << 5)
> +#define NFC_RANDOM_EN (1 << 9)
> +#define NFC_RANDOM_DIRECTION (1 << 10)
> +#define NFC_ECC_MODE_SHIFT 12
> +#define NFC_ECC_MODE (0xf << NFC_ECC_MODE_SHIFT)
> +#define NFC_RANDOM_SEED (0x7fff << 16)
> +
> +
> +
> +enum sunxi_nand_rb_type {
> + RB_NONE,
> + RB_NATIVE,
> + RB_GPIO,
> +};
> +
> +struct sunxi_nand_rb {
> + enum sunxi_nand_rb_type type;
> + union {
> + int gpio;
> + int nativeid;
> + } info;
> +};
> +
> +struct sunxi_nand_chip_sel {
> + u8 cs;
> + struct sunxi_nand_rb rb;
> +};
> +
> +#define DEFAULT_NAME_FORMAT "nand@%d"
> +#define MAX_NAME_SIZE (sizeof("nand@") + 2)
> +
> +struct sunxi_nand_chip {
> + struct list_head node;
> + struct nand_chip nand;
> + struct mtd_info mtd;
> + char default_name[MAX_NAME_SIZE];
> + unsigned long clk_rate;
> + int selected;
> + int nsels;
> + struct sunxi_nand_chip_sel sels[0];
> +};
> +
> +static inline struct sunxi_nand_chip *to_sunxi_nand(struct mtd_info *mtd)
> +{
> + return container_of(mtd, struct sunxi_nand_chip, mtd);
> +}
> +
> +struct sunxi_nfc {
> + struct nand_hw_control controller;
> + void __iomem *regs;
> + int irq;
> + struct clk *ahb_clk;
> + struct clk *sclk;
> + unsigned long assigned_cs;
> + unsigned long clk_rate;
> + struct list_head chips;
> + struct completion complete;
> +};
> +
> +static inline struct sunxi_nfc *to_sunxi_nfc(struct nand_hw_control *ctrl)
> +{
> + return container_of(ctrl, struct sunxi_nfc, controller);
> +}
> +
> +static irqreturn_t sunxi_nfc_interrupt(int irq, void *dev_id)
> +{
> + struct sunxi_nfc *nfc = dev_id;
> + u32 st = readl(nfc->regs + NFC_REG_ST);
> + u32 ien = readl(nfc->regs + NFC_REG_INT);
> +
> + if (!(ien & st))
> + return IRQ_NONE;
> +
> + if ((ien & st) == ien)
> + complete(&nfc->complete);
> +
> + writel(st & NFC_INT_MASK, nfc->regs + NFC_REG_ST);
> + writel(~st & ien & NFC_INT_MASK, nfc->regs + NFC_REG_INT);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int sunxi_nfc_wait_int(struct sunxi_nfc *nfc, u32 flags,
> + unsigned int timeout_ms)
> +{
> + init_completion(&nfc->complete);
> +
> + writel(flags, nfc->regs + NFC_REG_INT);
> + if (!timeout_ms)
> + wait_for_completion(&nfc->complete);
> + else if (!wait_for_completion_timeout(&nfc->complete,
> + msecs_to_jiffies(timeout_ms)))
> + return -ETIMEDOUT;
> +
> + return 0;
> +}
> +
> +static int sunxi_nfc_dev_ready(struct mtd_info *mtd)
> +{
> + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(mtd);
> + struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller);
> + struct sunxi_nand_rb *rb;
> + unsigned long timeo = (sunxi_nand->nand.state == FL_ERASING ? 400 : 20);
> + int ret;
> +
> + if (sunxi_nand->selected < 0)
> + return 0;
> +
> + rb = &sunxi_nand->sels[sunxi_nand->selected].rb;
> +
> + switch (rb->type) {
> + case RB_NATIVE:
> + ret = !!(readl(nfc->regs + NFC_REG_ST) &
> + (NFC_RB_STATE0 << rb->info.nativeid));
> + if (ret)
> + break;
> +
> + sunxi_nfc_wait_int(nfc, NFC_RB_B2R, timeo);
> + ret = !!(readl(nfc->regs + NFC_REG_ST) &
> + (NFC_RB_STATE0 << rb->info.nativeid));
> + break;
> + case RB_GPIO:
> + ret = gpio_get_value(rb->info.gpio);
> + break;
> + case RB_NONE:
> + default:
> + ret = 0;
> + dev_err(&mtd->dev, "cannot check R/B NAND status!");
> + break;
> + }
> +
> + return ret;
> +}
> +
> +static void sunxi_nfc_select_chip(struct mtd_info *mtd, int chip)
> +{
> + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(mtd);
> + struct nand_chip *nand = &sunxi_nand->nand;
> + struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller);
> + struct sunxi_nand_chip_sel *sel;
> + u32 ctl;
> +
> + if (chip > 0 && chip >= sunxi_nand->nsels)
> + return;
> +
> + if (chip == sunxi_nand->selected)
> + return;
> +
> + ctl = readl(nfc->regs + NFC_REG_CTL) &
> + ~(NFC_CE_SEL | NFC_RB_SEL | NFC_EN);
> +
> + if (chip >= 0) {
> + sel = &sunxi_nand->sels[chip];
> +
> + ctl |= (sel->cs << 24) | NFC_EN |
> + (((nand->page_shift - 10) & 0xf) << 8);
> + if (sel->rb.type == RB_NONE) {
> + nand->dev_ready = NULL;
> + } else {
> + nand->dev_ready = sunxi_nfc_dev_ready;
> + if (sel->rb.type == RB_NATIVE)
> + ctl |= (sel->rb.info.nativeid << 3);
> + }
> +
> + writel(mtd->writesize, nfc->regs + NFC_REG_SPARE_AREA);
> +
> + if (nfc->clk_rate != sunxi_nand->clk_rate) {
> + clk_set_rate(nfc->sclk, sunxi_nand->clk_rate);
> + nfc->clk_rate = sunxi_nand->clk_rate;
> + }
> + }
> +
> + writel(ctl, nfc->regs + NFC_REG_CTL);
> +
> + sunxi_nand->selected = chip;
> +}
> +
> +static void sunxi_nfc_read_buf(struct mtd_info *mtd, uint8_t *buf, int len)
> +{
> + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(mtd);
> + struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller);
> + int cnt;
> + int offs = 0;
> + u32 tmp;
> +
> + while (len > offs) {
> + cnt = len - offs;
> + if (cnt > 1024)
> + cnt = 1024;
> +
> + while ((readl(nfc->regs + NFC_REG_ST) & NFC_CMD_FIFO_STATUS))
> + ;
> + writel(cnt, nfc->regs + NFC_REG_CNT);
> + tmp = NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD;
> + writel(tmp, nfc->regs + NFC_REG_CMD);
> + sunxi_nfc_wait_int(nfc, NFC_CMD_INT_FLAG, 0);
> + if (buf)
> + memcpy_fromio(buf + offs, nfc->regs + NFC_RAM0_BASE,
> + cnt);
> + offs += cnt;
> + }
> +}
> +
> +static void sunxi_nfc_write_buf(struct mtd_info *mtd, const uint8_t *buf,
> + int len)
> +{
> + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(mtd);
> + struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller);
> + int cnt;
> + int offs = 0;
> + u32 tmp;
> +
> + while (len > offs) {
> + cnt = len - offs;
> + if (cnt > 1024)
> + cnt = 1024;
> +
> + while ((readl(nfc->regs + NFC_REG_ST) & NFC_CMD_FIFO_STATUS))
> + ;
> + writel(cnt, nfc->regs + NFC_REG_CNT);
> + memcpy_toio(nfc->regs + NFC_RAM0_BASE, buf + offs, cnt);
> + tmp = NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD |
> + NFC_ACCESS_DIR;
> + writel(tmp, nfc->regs + NFC_REG_CMD);
> + sunxi_nfc_wait_int(nfc, NFC_CMD_INT_FLAG, 0);
> + offs += cnt;
> + }
> +}
> +
> +static uint8_t sunxi_nfc_read_byte(struct mtd_info *mtd)
> +{
> + uint8_t ret;
> +
> + sunxi_nfc_read_buf(mtd, &ret, 1);
> +
> + return ret;
> +}
> +
> +static void sunxi_nfc_cmd_ctrl(struct mtd_info *mtd, int dat,
> + unsigned int ctrl)
> +{
> + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(mtd);
> + struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller);
> + u32 tmp;
> +
> + while ((readl(nfc->regs + NFC_REG_ST) & NFC_CMD_FIFO_STATUS))
> + ;
> +
> + if (ctrl & NAND_CTRL_CHANGE) {
> + tmp = readl(nfc->regs + NFC_REG_CTL);
> + if (ctrl & NAND_NCE)
> + tmp |= NFC_CE_CTL;
> + else
> + tmp &= ~NFC_CE_CTL;
> + writel(tmp, nfc->regs + NFC_REG_CTL);
> + }
> +
> + if (dat == NAND_CMD_NONE)
> + return;
> +
> + if (ctrl & NAND_CLE) {
> + writel(NFC_SEND_CMD1 | dat, nfc->regs + NFC_REG_CMD);
> + } else {
> + writel(dat, nfc->regs + NFC_REG_ADDR_LOW);
> + writel(NFC_SEND_ADR, nfc->regs + NFC_REG_CMD);
> + }
> +
> + sunxi_nfc_wait_int(nfc, NFC_CMD_INT_FLAG, 0);
> +}
> +
> +static int sunxi_nand_chip_set_timings(struct sunxi_nand_chip *chip,
> + const struct nand_sdr_timings *timings)
> +{
> + u32 min_clk_period = 0;
> +
> + /* T1 <=> tCLS */
> + if (timings->tCLS_min > min_clk_period)
> + min_clk_period = timings->tCLS_min;
> +
> + /* T2 <=> tCLH */
> + if (timings->tCLH_min > min_clk_period)
> + min_clk_period = timings->tCLH_min;
> +
> + /* T3 <=> tCS */
> + if (timings->tCS_min > min_clk_period)
> + min_clk_period = timings->tCS_min;
> +
> + /* T4 <=> tCH */
> + if (timings->tCH_min > min_clk_period)
> + min_clk_period = timings->tCH_min;
> +
> + /* T5 <=> tWP */
> + if (timings->tWP_min > min_clk_period)
> + min_clk_period = timings->tWP_min;
> +
> + /* T6 <=> tWH */
> + if (timings->tWH_min > min_clk_period)
> + min_clk_period = timings->tWH_min;
> +
> + /* T7 <=> tALS */
> + if (timings->tALS_min > min_clk_period)
> + min_clk_period = timings->tALS_min;
> +
> + /* T8 <=> tDS */
> + if (timings->tDS_min > min_clk_period)
> + min_clk_period = timings->tDS_min;
> +
> + /* T9 <=> tDH */
> + if (timings->tDH_min > min_clk_period)
> + min_clk_period = timings->tDH_min;
> +
> + /* T10 <=> tRR */
> + if (timings->tRR_min > (min_clk_period * 3))
> + min_clk_period = (timings->tRR_min + 2) / 3;
> +
> + /* T11 <=> tALH */
> + if (timings->tALH_min > min_clk_period)
> + min_clk_period = timings->tALH_min;
> +
> + /* T12 <=> tRP */
> + if (timings->tRP_min > min_clk_period)
> + min_clk_period = timings->tRP_min;
> +
> + /* T13 <=> tREH */
> + if (timings->tREH_min > min_clk_period)
> + min_clk_period = timings->tREH_min;
> +
> + /* T14 <=> tRC */
> + if (timings->tRC_min > (min_clk_period * 2))
> + min_clk_period = (timings->tRC_min + 1) / 2;
> +
> + /* T15 <=> tWC */
> + if (timings->tWC_min > (min_clk_period * 2))
> + min_clk_period = (timings->tWC_min + 1) / 2;
> +
> +
> + /* min_clk_period = (NAND-clk-period * 2) */
> + if (min_clk_period < 1000)
> + min_clk_period = 1000;
> +
> + min_clk_period /= 1000;
> + chip->clk_rate = (2 * 1000000000) / min_clk_period;
> +
> + /* TODO: configure T16-T19 */
> +
> + return 0;
> +}
> +
> +static int sunxi_nand_chip_init_timings(struct sunxi_nand_chip *chip,
> + struct device_node *np)
> +{
> + const struct nand_sdr_timings *timings;
> + int ret;
> +
> + ret = onfi_get_async_timing_mode(&chip->nand);
> + if (ret == ONFI_TIMING_MODE_UNKNOWN) {
> + ret = of_get_nand_onfi_timing_mode(np);
> + if (ret < 0)
> + return ret;
> + }
> +
> + ret = fls(ret);
> + if (!ret)
> + return -EINVAL;
> +
> + timings = onfi_async_timing_mode_to_sdr_timings(ret - 1);
> + if (IS_ERR(timings))
> + return PTR_ERR(timings);
> +
> + return sunxi_nand_chip_set_timings(chip, timings);
> +}
> +
> +static int sunxi_nand_chip_init(struct device *dev, struct sunxi_nfc *nfc,
> + struct device_node *np)
> +{
> + const struct nand_sdr_timings *timings;
> + struct sunxi_nand_chip *chip;
> + struct mtd_part_parser_data ppdata;
> + struct mtd_info *mtd;
> + struct nand_chip *nand;
> + u32 strength;
> + u32 blk_size;
> + int nsels;
> + int ret;
> + int i;
> + u32 tmp;
> +
> + if (!of_get_property(np, "reg", &nsels))
> + return -EINVAL;
> +
> + nsels /= sizeof(u32);
> + if (!nsels)
> + return -EINVAL;
> +
> + chip = devm_kzalloc(dev,
> + sizeof(*chip) +
> + (nsels * sizeof(struct sunxi_nand_chip_sel)),
> + GFP_KERNEL);
> + if (!chip)
> + return -ENOMEM;
> +
> + chip->nsels = nsels;
> + chip->selected = -1;
> +
> + for (i = 0; i < nsels; i++) {
> + ret = of_property_read_u32_index(np, "reg", i, &tmp);
> + if (ret)
> + return ret;
> +
> + if (tmp > 7)
> + return -EINVAL;
> +
> + if (test_and_set_bit(tmp, &nfc->assigned_cs))
> + return -EINVAL;
> +
> + chip->sels[i].cs = tmp;
> +
> + if (!of_property_read_u32_index(np, "allwinner,rb", i, &tmp) &&
> + tmp < 2) {
> + chip->sels[i].rb.type = RB_NATIVE;
> + chip->sels[i].rb.info.nativeid = tmp;
> + } else {
> + ret = of_get_named_gpio(np, "rb-gpios", i);
> + if (ret >= 0) {
> + tmp = ret;
> + chip->sels[i].rb.type = RB_GPIO;
> + chip->sels[i].rb.info.gpio = tmp;
> + ret = devm_gpio_request(dev, tmp, "nand-rb");
> + if (ret)
> + return ret;
> +
> + ret = gpio_direction_input(tmp);
> + if (ret)
> + return ret;
> + } else {
> + chip->sels[i].rb.type = RB_NONE;
> + }
> + }
> + }
> +
> + timings = onfi_async_timing_mode_to_sdr_timings(0);
> + if (IS_ERR(timings))
> + return PTR_ERR(timings);
> +
> + ret = sunxi_nand_chip_set_timings(chip, timings);
> +
> + nand = &chip->nand;
> + nand->controller = &nfc->controller;
> + nand->select_chip = sunxi_nfc_select_chip;
> + nand->cmd_ctrl = sunxi_nfc_cmd_ctrl;
> + nand->read_buf = sunxi_nfc_read_buf;
> + nand->write_buf = sunxi_nfc_write_buf;
> + nand->read_byte = sunxi_nfc_read_byte;
> +
> + nand->ecc.mode = of_get_nand_ecc_mode(np);
> + if (of_get_nand_on_flash_bbt(np))
> + nand->bbt_options |= NAND_BBT_USE_FLASH;
> +
> + mtd = &chip->mtd;
> + mtd->priv = nand;
> + mtd->owner = THIS_MODULE;
> +
> + ret = nand_scan_ident(mtd, nsels, NULL);
> + if (ret)
> + return ret;
> +
> + ret = sunxi_nand_chip_init_timings(chip, np);
> + if (ret)
> + return ret;
> +
> + if (nand->ecc.mode == NAND_ECC_SOFT_BCH) {
> + if (!of_get_nand_ecc_level(np, &strength, &blk_size)) {
> + nand->ecc_step_ds = blk_size;
> + nand->ecc_strength_ds = strength;
> + }
> +
> + nand->ecc.size = nand->ecc_step_ds;
> + nand->ecc.bytes = (((nand->ecc_strength_ds *
> + fls(8 * nand->ecc_step_ds)) + 7) / 8);
> + }
> +
> + ret = nand_scan_tail(mtd);
> + if (ret)
> + return ret;
> +
> + if (of_property_read_string(np, "nand-name", &mtd->name)) {
> + snprintf(chip->default_name, MAX_NAME_SIZE,
> + DEFAULT_NAME_FORMAT, chip->sels[i].cs);
> + mtd->name = chip->default_name;
> + }
> +
> + ppdata.of_node = np;
> + ret = mtd_device_parse_register(mtd, NULL, &ppdata, NULL, 0);
> + if (!ret)
> + return ret;
> +
> + list_add_tail(&chip->node, &nfc->chips);
> +
> + return 0;
> +}
> +
> +static int sunxi_nand_chips_init(struct device *dev, struct sunxi_nfc *nfc)
> +{
> + struct device_node *np = dev->of_node;
> + struct device_node *nand_np;
> + int nchips = of_get_child_count(np);
> + int ret;
> +
> + if (nchips > 8)
> + return -EINVAL;
> +
> + for_each_child_of_node(np, nand_np) {
> + ret = sunxi_nand_chip_init(dev, nfc, nand_np);
> + if (ret)
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int sunxi_nfc_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct resource *r;
> + struct sunxi_nfc *nfc;
> + int ret;
> +
> + nfc = devm_kzalloc(dev, sizeof(*nfc), GFP_KERNEL);
> + if (!nfc) {
> + dev_err(dev, "failed to allocate NFC struct\n");
> + return -ENOMEM;
> + }
> +
> + spin_lock_init(&nfc->controller.lock);
> + init_waitqueue_head(&nfc->controller.wq);
> + INIT_LIST_HEAD(&nfc->chips);
> +
> + r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + nfc->regs = devm_ioremap_resource(dev, r);
> + if (IS_ERR(nfc->regs)) {
> + dev_err(dev, "failed to remap iomem\n");
> + return PTR_ERR(nfc->regs);
> + }
> +
> + nfc->irq = platform_get_irq(pdev, 0);
> + if (nfc->irq < 0) {
> + dev_err(dev, "failed to retrieve irq\n");
> + return nfc->irq;
> + }
> +
> + nfc->ahb_clk = devm_clk_get(dev, "ahb_clk");
> + if (IS_ERR(nfc->ahb_clk)) {
> + dev_err(dev, "failed to retrieve ahb_clk\n");
> + return PTR_ERR(nfc->ahb_clk);
> + }
> +
> + ret = clk_prepare_enable(nfc->ahb_clk);
> + if (ret)
> + return ret;
> +
> + nfc->sclk = devm_clk_get(dev, "sclk");
> + if (IS_ERR(nfc->sclk)) {
> + dev_err(dev, "failed to retrieve nand_clk\n");
> + ret = PTR_ERR(nfc->sclk);
> + goto out_ahb_clk_unprepare;
> + }
> +
> + ret = clk_prepare_enable(nfc->sclk);
> + if (ret)
> + goto out_ahb_clk_unprepare;
> +
> + /* Reset NFC */
> + writel(readl(nfc->regs + NFC_REG_CTL) | NFC_RESET,
> + nfc->regs + NFC_REG_CTL);
> + while (readl(nfc->regs + NFC_REG_CTL) & NFC_RESET)
> + ;
> +
> + writel(0, nfc->regs + NFC_REG_INT);
> + ret = devm_request_irq(dev, nfc->irq, sunxi_nfc_interrupt,
> + 0, "sunxi-nand", nfc);
> + if (ret)
> + goto out_sclk_unprepare;
> +
> + platform_set_drvdata(pdev, nfc);
> +
> + writel(0x100, nfc->regs + NFC_REG_TIMING_CTL);
> + writel(0x7ff, nfc->regs + NFC_REG_TIMING_CFG);
> +
> + ret = sunxi_nand_chips_init(dev, nfc);
> + if (ret) {
> + dev_err(dev, "failed to init nand chips\n");
> + goto out_sclk_unprepare;
> + }
> +
> + return 0;
> +
> +out_sclk_unprepare:
> + clk_disable_unprepare(nfc->sclk);
> +out_ahb_clk_unprepare:
> + clk_disable_unprepare(nfc->ahb_clk);
> +
> + return ret;
> +}
> +
> +static const struct of_device_id sunxi_nfc_ids[] = {
> + { .compatible = "allwinner,sun4i-nand" },
> + { /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, sunxi_nfc_ids);
> +
> +static struct platform_driver sunxi_nfc_driver = {
> + .driver = {
> + .name = "sunxi_nand",
> + .owner = THIS_MODULE,
> + .of_match_table = of_match_ptr(sunxi_nfc_ids),
> + },
> + .probe = sunxi_nfc_probe,
> +};
> +module_platform_driver(sunxi_nfc_driver);
> +
> +MODULE_LICENSE("GPL v2");
> +MODULE_AUTHOR("Boris BREZILLON");
> +MODULE_DESCRIPTION("Allwinner NAND Flash Controller driver");
> +MODULE_ALIAS("platform:sunxi_nfc");
> --
> 1.7.9.5
>
--
FTTC broadband for 0.8mile line: 5.8Mbps down 500kbps up. Estimation
in database were 13.1 to 19Mbit for a good line, about 7.5+ for a bad.
Estimate before purchase was "up to 13.2Mbit".
Hello Russel,
On 30/01/2014 15:36, Russell King - ARM Linux wrote:
> Boris,
>
> Can you please explain to me why you mail all your patches _To:_ me?
> As in, why do I appear in the To: line of all the patches you seem to
> mail out, whether or not they're relevant to me. I see this very
> regularly from you - virtually all patches I see on the LAKML mailing
> list from you are always sent To: me as well.
>
> Take for instance this one. It doesn't match up with anything in
> MAINTAINERS for me. It doesn't even touch a file that I've touched.
> Yet somehow you think that I should be in the To: header.
>
> Being in the To: header means that you expect the recipient to do
> something with your email. The Cc: header is to circulate copies of
> your email to people who may be interested.
>
> I'm neither for this stuff. Please stop this.
Sorry for the inconvenience.
I'm using get_maintainer.pl script to retrieve the list of concerned
people, and you appears in that list for patches 10,11,12 and 14 of this
series.
Moreover I was told to send the whole series (not just specific patches
to people who might be concerned).
I obviously misuse this script, so please tell me how I should know which
patch I should send to whom.
Thanks.
Best Regards,
Boris
>
> Thanks.
>
> On Thu, Jan 30, 2014 at 02:39:36PM +0100, Boris BREZILLON wrote:
>> Add support for the sunxi NAND Flash Controller (NFC).
>>
>> Signed-off-by: Boris BREZILLON <[email protected]>
>> ---
>> Hello,
>>
>> This version fixes a bug in the R/B GPIO config block.
>> The timing config order is now respected, but I'll wait for Jason work
>> regarding timing config in NAND core code before posting the 3rd version
>> of this series.
>>
>> Best Regards,
>>
>> Boris
>>
>> Changes since v2:
>> - fix R/B GPIO retrieval/config bug
>> - fix timings configuration order (set mode 0 -> scan -> set best supported
>> mode)
>>
>> drivers/mtd/nand/Kconfig | 6 +
>> drivers/mtd/nand/Makefile | 1 +
>> drivers/mtd/nand/sunxi_nand.c | 758 +++++++++++++++++++++++++++++++++++++++++
>> 3 files changed, 765 insertions(+)
>> create mode 100644 drivers/mtd/nand/sunxi_nand.c
>>
>> diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig
>> index 93ae6a6..784dd42 100644
>> --- a/drivers/mtd/nand/Kconfig
>> +++ b/drivers/mtd/nand/Kconfig
>> @@ -510,4 +510,10 @@ config MTD_NAND_XWAY
>> Enables support for NAND Flash chips on Lantiq XWAY SoCs. NAND is attached
>> to the External Bus Unit (EBU).
>>
>> +config MTD_NAND_SUNXI
>> + tristate "Support for NAND on Allwinner SoCs"
>> + depends on ARCH_SUNXI
>> + help
>> + Enables support for NAND Flash chips on Allwinner SoCs.
>> +
>> endif # MTD_NAND
>> diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile
>> index bbea7a6..e3b4a34 100644
>> --- a/drivers/mtd/nand/Makefile
>> +++ b/drivers/mtd/nand/Makefile
>> @@ -49,5 +49,6 @@ obj-$(CONFIG_MTD_NAND_JZ4740) += jz4740_nand.o
>> obj-$(CONFIG_MTD_NAND_GPMI_NAND) += gpmi-nand/
>> obj-$(CONFIG_MTD_NAND_XWAY) += xway_nand.o
>> obj-$(CONFIG_MTD_NAND_BCM47XXNFLASH) += bcm47xxnflash/
>> +obj-$(CONFIG_MTD_NAND_SUNXI) += sunxi_nand.o
>>
>> nand-objs := nand_base.o nand_bbt.o
>> diff --git a/drivers/mtd/nand/sunxi_nand.c b/drivers/mtd/nand/sunxi_nand.c
>> new file mode 100644
>> index 0000000..1014b2a
>> --- /dev/null
>> +++ b/drivers/mtd/nand/sunxi_nand.c
>> @@ -0,0 +1,758 @@
>> +/*
>> + * Copyright (C) 2013 Boris BREZILLON <[email protected]>
>> + *
>> + * Derived from:
>> + * https://github.com/yuq/sunxi-nfc-mtd
>> + * Copyright (C) 2013 Qiang Yu <[email protected]>
>> + *
>> + * https://github.com/hno/Allwinner-Info
>> + * Copyright (C) 2013 Henrik Nordstr?m <Henrik Nordstr?m>
>> + *
>> + * Copyright (C) 2013 Dmitriy B. <[email protected]>
>> + * Copyright (C) 2013 Sergey Lapin <[email protected]>
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License as published by
>> + * the Free Software Foundation; either version 2 of the License, or
>> + * (at your option) any later version.
>> + *
>> + * This program is distributed in the hope that it will be useful,
>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
>> + * GNU General Public License for more details.
>> + */
>> +
>> +#include <linux/dma-mapping.h>
>> +#include <linux/slab.h>
>> +#include <linux/module.h>
>> +#include <linux/moduleparam.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/of.h>
>> +#include <linux/of_device.h>
>> +#include <linux/of_gpio.h>
>> +#include <linux/of_mtd.h>
>> +#include <linux/mtd/mtd.h>
>> +#include <linux/mtd/nand.h>
>> +#include <linux/mtd/partitions.h>
>> +#include <linux/clk.h>
>> +#include <linux/delay.h>
>> +#include <linux/dmaengine.h>
>> +#include <linux/gpio.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/io.h>
>> +
>> +#define NFC_REG_CTL 0x0000
>> +#define NFC_REG_ST 0x0004
>> +#define NFC_REG_INT 0x0008
>> +#define NFC_REG_TIMING_CTL 0x000C
>> +#define NFC_REG_TIMING_CFG 0x0010
>> +#define NFC_REG_ADDR_LOW 0x0014
>> +#define NFC_REG_ADDR_HIGH 0x0018
>> +#define NFC_REG_SECTOR_NUM 0x001C
>> +#define NFC_REG_CNT 0x0020
>> +#define NFC_REG_CMD 0x0024
>> +#define NFC_REG_RCMD_SET 0x0028
>> +#define NFC_REG_WCMD_SET 0x002C
>> +#define NFC_REG_IO_DATA 0x0030
>> +#define NFC_REG_ECC_CTL 0x0034
>> +#define NFC_REG_ECC_ST 0x0038
>> +#define NFC_REG_DEBUG 0x003C
>> +#define NFC_REG_ECC_CNT0 0x0040
>> +#define NFC_REG_ECC_CNT1 0x0044
>> +#define NFC_REG_ECC_CNT2 0x0048
>> +#define NFC_REG_ECC_CNT3 0x004c
>> +#define NFC_REG_USER_DATA_BASE 0x0050
>> +#define NFC_REG_SPARE_AREA 0x00A0
>> +#define NFC_RAM0_BASE 0x0400
>> +#define NFC_RAM1_BASE 0x0800
>> +
>> +/*define bit use in NFC_CTL*/
>> +#define NFC_EN (1 << 0)
>> +#define NFC_RESET (1 << 1)
>> +#define NFC_BUS_WIDYH (1 << 2)
>> +#define NFC_RB_SEL (1 << 3)
>> +#define NFC_CE_SEL (7 << 24)
>> +#define NFC_CE_CTL (1 << 6)
>> +#define NFC_CE_CTL1 (1 << 7)
>> +#define NFC_PAGE_SIZE (0xf << 8)
>> +#define NFC_SAM (1 << 12)
>> +#define NFC_RAM_METHOD (1 << 14)
>> +#define NFC_DEBUG_CTL (1 << 31)
>> +
>> +/*define bit use in NFC_ST*/
>> +#define NFC_RB_B2R (1 << 0)
>> +#define NFC_CMD_INT_FLAG (1 << 1)
>> +#define NFC_DMA_INT_FLAG (1 << 2)
>> +#define NFC_CMD_FIFO_STATUS (1 << 3)
>> +#define NFC_STA (1 << 4)
>> +#define NFC_NATCH_INT_FLAG (1 << 5)
>> +#define NFC_RB_STATE0 (1 << 8)
>> +#define NFC_RB_STATE1 (1 << 9)
>> +#define NFC_RB_STATE2 (1 << 10)
>> +#define NFC_RB_STATE3 (1 << 11)
>> +
>> +/*define bit use in NFC_INT*/
>> +#define NFC_B2R_INT_ENABLE (1 << 0)
>> +#define NFC_CMD_INT_ENABLE (1 << 1)
>> +#define NFC_DMA_INT_ENABLE (1 << 2)
>> +#define NFC_INT_MASK (NFC_B2R_INT_ENABLE | \
>> + NFC_CMD_INT_ENABLE | \
>> + NFC_DMA_INT_ENABLE)
>> +
>> +
>> +/*define bit use in NFC_CMD*/
>> +#define NFC_CMD_LOW_BYTE (0xff << 0)
>> +#define NFC_CMD_HIGH_BYTE (0xff << 8)
>> +#define NFC_ADR_NUM (0x7 << 16)
>> +#define NFC_SEND_ADR (1 << 19)
>> +#define NFC_ACCESS_DIR (1 << 20)
>> +#define NFC_DATA_TRANS (1 << 21)
>> +#define NFC_SEND_CMD1 (1 << 22)
>> +#define NFC_WAIT_FLAG (1 << 23)
>> +#define NFC_SEND_CMD2 (1 << 24)
>> +#define NFC_SEQ (1 << 25)
>> +#define NFC_DATA_SWAP_METHOD (1 << 26)
>> +#define NFC_ROW_AUTO_INC (1 << 27)
>> +#define NFC_SEND_CMD3 (1 << 28)
>> +#define NFC_SEND_CMD4 (1 << 29)
>> +#define NFC_CMD_TYPE (3 << 30)
>> +
>> +/* define bit use in NFC_RCMD_SET*/
>> +#define NFC_READ_CMD (0xff << 0)
>> +#define NFC_RANDOM_READ_CMD0 (0xff << 8)
>> +#define NFC_RANDOM_READ_CMD1 (0xff << 16)
>> +
>> +/*define bit use in NFC_WCMD_SET*/
>> +#define NFC_PROGRAM_CMD (0xff << 0)
>> +#define NFC_RANDOM_WRITE_CMD (0xff << 8)
>> +#define NFC_READ_CMD0 (0xff << 16)
>> +#define NFC_READ_CMD1 (0xff << 24)
>> +
>> +/*define bit use in NFC_ECC_CTL*/
>> +#define NFC_ECC_EN (1 << 0)
>> +#define NFC_ECC_PIPELINE (1 << 3)
>> +#define NFC_ECC_EXCEPTION (1 << 4)
>> +#define NFC_ECC_BLOCK_SIZE (1 << 5)
>> +#define NFC_RANDOM_EN (1 << 9)
>> +#define NFC_RANDOM_DIRECTION (1 << 10)
>> +#define NFC_ECC_MODE_SHIFT 12
>> +#define NFC_ECC_MODE (0xf << NFC_ECC_MODE_SHIFT)
>> +#define NFC_RANDOM_SEED (0x7fff << 16)
>> +
>> +
>> +
>> +enum sunxi_nand_rb_type {
>> + RB_NONE,
>> + RB_NATIVE,
>> + RB_GPIO,
>> +};
>> +
>> +struct sunxi_nand_rb {
>> + enum sunxi_nand_rb_type type;
>> + union {
>> + int gpio;
>> + int nativeid;
>> + } info;
>> +};
>> +
>> +struct sunxi_nand_chip_sel {
>> + u8 cs;
>> + struct sunxi_nand_rb rb;
>> +};
>> +
>> +#define DEFAULT_NAME_FORMAT "nand@%d"
>> +#define MAX_NAME_SIZE (sizeof("nand@") + 2)
>> +
>> +struct sunxi_nand_chip {
>> + struct list_head node;
>> + struct nand_chip nand;
>> + struct mtd_info mtd;
>> + char default_name[MAX_NAME_SIZE];
>> + unsigned long clk_rate;
>> + int selected;
>> + int nsels;
>> + struct sunxi_nand_chip_sel sels[0];
>> +};
>> +
>> +static inline struct sunxi_nand_chip *to_sunxi_nand(struct mtd_info *mtd)
>> +{
>> + return container_of(mtd, struct sunxi_nand_chip, mtd);
>> +}
>> +
>> +struct sunxi_nfc {
>> + struct nand_hw_control controller;
>> + void __iomem *regs;
>> + int irq;
>> + struct clk *ahb_clk;
>> + struct clk *sclk;
>> + unsigned long assigned_cs;
>> + unsigned long clk_rate;
>> + struct list_head chips;
>> + struct completion complete;
>> +};
>> +
>> +static inline struct sunxi_nfc *to_sunxi_nfc(struct nand_hw_control *ctrl)
>> +{
>> + return container_of(ctrl, struct sunxi_nfc, controller);
>> +}
>> +
>> +static irqreturn_t sunxi_nfc_interrupt(int irq, void *dev_id)
>> +{
>> + struct sunxi_nfc *nfc = dev_id;
>> + u32 st = readl(nfc->regs + NFC_REG_ST);
>> + u32 ien = readl(nfc->regs + NFC_REG_INT);
>> +
>> + if (!(ien & st))
>> + return IRQ_NONE;
>> +
>> + if ((ien & st) == ien)
>> + complete(&nfc->complete);
>> +
>> + writel(st & NFC_INT_MASK, nfc->regs + NFC_REG_ST);
>> + writel(~st & ien & NFC_INT_MASK, nfc->regs + NFC_REG_INT);
>> +
>> + return IRQ_HANDLED;
>> +}
>> +
>> +static int sunxi_nfc_wait_int(struct sunxi_nfc *nfc, u32 flags,
>> + unsigned int timeout_ms)
>> +{
>> + init_completion(&nfc->complete);
>> +
>> + writel(flags, nfc->regs + NFC_REG_INT);
>> + if (!timeout_ms)
>> + wait_for_completion(&nfc->complete);
>> + else if (!wait_for_completion_timeout(&nfc->complete,
>> + msecs_to_jiffies(timeout_ms)))
>> + return -ETIMEDOUT;
>> +
>> + return 0;
>> +}
>> +
>> +static int sunxi_nfc_dev_ready(struct mtd_info *mtd)
>> +{
>> + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(mtd);
>> + struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller);
>> + struct sunxi_nand_rb *rb;
>> + unsigned long timeo = (sunxi_nand->nand.state == FL_ERASING ? 400 : 20);
>> + int ret;
>> +
>> + if (sunxi_nand->selected < 0)
>> + return 0;
>> +
>> + rb = &sunxi_nand->sels[sunxi_nand->selected].rb;
>> +
>> + switch (rb->type) {
>> + case RB_NATIVE:
>> + ret = !!(readl(nfc->regs + NFC_REG_ST) &
>> + (NFC_RB_STATE0 << rb->info.nativeid));
>> + if (ret)
>> + break;
>> +
>> + sunxi_nfc_wait_int(nfc, NFC_RB_B2R, timeo);
>> + ret = !!(readl(nfc->regs + NFC_REG_ST) &
>> + (NFC_RB_STATE0 << rb->info.nativeid));
>> + break;
>> + case RB_GPIO:
>> + ret = gpio_get_value(rb->info.gpio);
>> + break;
>> + case RB_NONE:
>> + default:
>> + ret = 0;
>> + dev_err(&mtd->dev, "cannot check R/B NAND status!");
>> + break;
>> + }
>> +
>> + return ret;
>> +}
>> +
>> +static void sunxi_nfc_select_chip(struct mtd_info *mtd, int chip)
>> +{
>> + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(mtd);
>> + struct nand_chip *nand = &sunxi_nand->nand;
>> + struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller);
>> + struct sunxi_nand_chip_sel *sel;
>> + u32 ctl;
>> +
>> + if (chip > 0 && chip >= sunxi_nand->nsels)
>> + return;
>> +
>> + if (chip == sunxi_nand->selected)
>> + return;
>> +
>> + ctl = readl(nfc->regs + NFC_REG_CTL) &
>> + ~(NFC_CE_SEL | NFC_RB_SEL | NFC_EN);
>> +
>> + if (chip >= 0) {
>> + sel = &sunxi_nand->sels[chip];
>> +
>> + ctl |= (sel->cs << 24) | NFC_EN |
>> + (((nand->page_shift - 10) & 0xf) << 8);
>> + if (sel->rb.type == RB_NONE) {
>> + nand->dev_ready = NULL;
>> + } else {
>> + nand->dev_ready = sunxi_nfc_dev_ready;
>> + if (sel->rb.type == RB_NATIVE)
>> + ctl |= (sel->rb.info.nativeid << 3);
>> + }
>> +
>> + writel(mtd->writesize, nfc->regs + NFC_REG_SPARE_AREA);
>> +
>> + if (nfc->clk_rate != sunxi_nand->clk_rate) {
>> + clk_set_rate(nfc->sclk, sunxi_nand->clk_rate);
>> + nfc->clk_rate = sunxi_nand->clk_rate;
>> + }
>> + }
>> +
>> + writel(ctl, nfc->regs + NFC_REG_CTL);
>> +
>> + sunxi_nand->selected = chip;
>> +}
>> +
>> +static void sunxi_nfc_read_buf(struct mtd_info *mtd, uint8_t *buf, int len)
>> +{
>> + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(mtd);
>> + struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller);
>> + int cnt;
>> + int offs = 0;
>> + u32 tmp;
>> +
>> + while (len > offs) {
>> + cnt = len - offs;
>> + if (cnt > 1024)
>> + cnt = 1024;
>> +
>> + while ((readl(nfc->regs + NFC_REG_ST) & NFC_CMD_FIFO_STATUS))
>> + ;
>> + writel(cnt, nfc->regs + NFC_REG_CNT);
>> + tmp = NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD;
>> + writel(tmp, nfc->regs + NFC_REG_CMD);
>> + sunxi_nfc_wait_int(nfc, NFC_CMD_INT_FLAG, 0);
>> + if (buf)
>> + memcpy_fromio(buf + offs, nfc->regs + NFC_RAM0_BASE,
>> + cnt);
>> + offs += cnt;
>> + }
>> +}
>> +
>> +static void sunxi_nfc_write_buf(struct mtd_info *mtd, const uint8_t *buf,
>> + int len)
>> +{
>> + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(mtd);
>> + struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller);
>> + int cnt;
>> + int offs = 0;
>> + u32 tmp;
>> +
>> + while (len > offs) {
>> + cnt = len - offs;
>> + if (cnt > 1024)
>> + cnt = 1024;
>> +
>> + while ((readl(nfc->regs + NFC_REG_ST) & NFC_CMD_FIFO_STATUS))
>> + ;
>> + writel(cnt, nfc->regs + NFC_REG_CNT);
>> + memcpy_toio(nfc->regs + NFC_RAM0_BASE, buf + offs, cnt);
>> + tmp = NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD |
>> + NFC_ACCESS_DIR;
>> + writel(tmp, nfc->regs + NFC_REG_CMD);
>> + sunxi_nfc_wait_int(nfc, NFC_CMD_INT_FLAG, 0);
>> + offs += cnt;
>> + }
>> +}
>> +
>> +static uint8_t sunxi_nfc_read_byte(struct mtd_info *mtd)
>> +{
>> + uint8_t ret;
>> +
>> + sunxi_nfc_read_buf(mtd, &ret, 1);
>> +
>> + return ret;
>> +}
>> +
>> +static void sunxi_nfc_cmd_ctrl(struct mtd_info *mtd, int dat,
>> + unsigned int ctrl)
>> +{
>> + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(mtd);
>> + struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller);
>> + u32 tmp;
>> +
>> + while ((readl(nfc->regs + NFC_REG_ST) & NFC_CMD_FIFO_STATUS))
>> + ;
>> +
>> + if (ctrl & NAND_CTRL_CHANGE) {
>> + tmp = readl(nfc->regs + NFC_REG_CTL);
>> + if (ctrl & NAND_NCE)
>> + tmp |= NFC_CE_CTL;
>> + else
>> + tmp &= ~NFC_CE_CTL;
>> + writel(tmp, nfc->regs + NFC_REG_CTL);
>> + }
>> +
>> + if (dat == NAND_CMD_NONE)
>> + return;
>> +
>> + if (ctrl & NAND_CLE) {
>> + writel(NFC_SEND_CMD1 | dat, nfc->regs + NFC_REG_CMD);
>> + } else {
>> + writel(dat, nfc->regs + NFC_REG_ADDR_LOW);
>> + writel(NFC_SEND_ADR, nfc->regs + NFC_REG_CMD);
>> + }
>> +
>> + sunxi_nfc_wait_int(nfc, NFC_CMD_INT_FLAG, 0);
>> +}
>> +
>> +static int sunxi_nand_chip_set_timings(struct sunxi_nand_chip *chip,
>> + const struct nand_sdr_timings *timings)
>> +{
>> + u32 min_clk_period = 0;
>> +
>> + /* T1 <=> tCLS */
>> + if (timings->tCLS_min > min_clk_period)
>> + min_clk_period = timings->tCLS_min;
>> +
>> + /* T2 <=> tCLH */
>> + if (timings->tCLH_min > min_clk_period)
>> + min_clk_period = timings->tCLH_min;
>> +
>> + /* T3 <=> tCS */
>> + if (timings->tCS_min > min_clk_period)
>> + min_clk_period = timings->tCS_min;
>> +
>> + /* T4 <=> tCH */
>> + if (timings->tCH_min > min_clk_period)
>> + min_clk_period = timings->tCH_min;
>> +
>> + /* T5 <=> tWP */
>> + if (timings->tWP_min > min_clk_period)
>> + min_clk_period = timings->tWP_min;
>> +
>> + /* T6 <=> tWH */
>> + if (timings->tWH_min > min_clk_period)
>> + min_clk_period = timings->tWH_min;
>> +
>> + /* T7 <=> tALS */
>> + if (timings->tALS_min > min_clk_period)
>> + min_clk_period = timings->tALS_min;
>> +
>> + /* T8 <=> tDS */
>> + if (timings->tDS_min > min_clk_period)
>> + min_clk_period = timings->tDS_min;
>> +
>> + /* T9 <=> tDH */
>> + if (timings->tDH_min > min_clk_period)
>> + min_clk_period = timings->tDH_min;
>> +
>> + /* T10 <=> tRR */
>> + if (timings->tRR_min > (min_clk_period * 3))
>> + min_clk_period = (timings->tRR_min + 2) / 3;
>> +
>> + /* T11 <=> tALH */
>> + if (timings->tALH_min > min_clk_period)
>> + min_clk_period = timings->tALH_min;
>> +
>> + /* T12 <=> tRP */
>> + if (timings->tRP_min > min_clk_period)
>> + min_clk_period = timings->tRP_min;
>> +
>> + /* T13 <=> tREH */
>> + if (timings->tREH_min > min_clk_period)
>> + min_clk_period = timings->tREH_min;
>> +
>> + /* T14 <=> tRC */
>> + if (timings->tRC_min > (min_clk_period * 2))
>> + min_clk_period = (timings->tRC_min + 1) / 2;
>> +
>> + /* T15 <=> tWC */
>> + if (timings->tWC_min > (min_clk_period * 2))
>> + min_clk_period = (timings->tWC_min + 1) / 2;
>> +
>> +
>> + /* min_clk_period = (NAND-clk-period * 2) */
>> + if (min_clk_period < 1000)
>> + min_clk_period = 1000;
>> +
>> + min_clk_period /= 1000;
>> + chip->clk_rate = (2 * 1000000000) / min_clk_period;
>> +
>> + /* TODO: configure T16-T19 */
>> +
>> + return 0;
>> +}
>> +
>> +static int sunxi_nand_chip_init_timings(struct sunxi_nand_chip *chip,
>> + struct device_node *np)
>> +{
>> + const struct nand_sdr_timings *timings;
>> + int ret;
>> +
>> + ret = onfi_get_async_timing_mode(&chip->nand);
>> + if (ret == ONFI_TIMING_MODE_UNKNOWN) {
>> + ret = of_get_nand_onfi_timing_mode(np);
>> + if (ret < 0)
>> + return ret;
>> + }
>> +
>> + ret = fls(ret);
>> + if (!ret)
>> + return -EINVAL;
>> +
>> + timings = onfi_async_timing_mode_to_sdr_timings(ret - 1);
>> + if (IS_ERR(timings))
>> + return PTR_ERR(timings);
>> +
>> + return sunxi_nand_chip_set_timings(chip, timings);
>> +}
>> +
>> +static int sunxi_nand_chip_init(struct device *dev, struct sunxi_nfc *nfc,
>> + struct device_node *np)
>> +{
>> + const struct nand_sdr_timings *timings;
>> + struct sunxi_nand_chip *chip;
>> + struct mtd_part_parser_data ppdata;
>> + struct mtd_info *mtd;
>> + struct nand_chip *nand;
>> + u32 strength;
>> + u32 blk_size;
>> + int nsels;
>> + int ret;
>> + int i;
>> + u32 tmp;
>> +
>> + if (!of_get_property(np, "reg", &nsels))
>> + return -EINVAL;
>> +
>> + nsels /= sizeof(u32);
>> + if (!nsels)
>> + return -EINVAL;
>> +
>> + chip = devm_kzalloc(dev,
>> + sizeof(*chip) +
>> + (nsels * sizeof(struct sunxi_nand_chip_sel)),
>> + GFP_KERNEL);
>> + if (!chip)
>> + return -ENOMEM;
>> +
>> + chip->nsels = nsels;
>> + chip->selected = -1;
>> +
>> + for (i = 0; i < nsels; i++) {
>> + ret = of_property_read_u32_index(np, "reg", i, &tmp);
>> + if (ret)
>> + return ret;
>> +
>> + if (tmp > 7)
>> + return -EINVAL;
>> +
>> + if (test_and_set_bit(tmp, &nfc->assigned_cs))
>> + return -EINVAL;
>> +
>> + chip->sels[i].cs = tmp;
>> +
>> + if (!of_property_read_u32_index(np, "allwinner,rb", i, &tmp) &&
>> + tmp < 2) {
>> + chip->sels[i].rb.type = RB_NATIVE;
>> + chip->sels[i].rb.info.nativeid = tmp;
>> + } else {
>> + ret = of_get_named_gpio(np, "rb-gpios", i);
>> + if (ret >= 0) {
>> + tmp = ret;
>> + chip->sels[i].rb.type = RB_GPIO;
>> + chip->sels[i].rb.info.gpio = tmp;
>> + ret = devm_gpio_request(dev, tmp, "nand-rb");
>> + if (ret)
>> + return ret;
>> +
>> + ret = gpio_direction_input(tmp);
>> + if (ret)
>> + return ret;
>> + } else {
>> + chip->sels[i].rb.type = RB_NONE;
>> + }
>> + }
>> + }
>> +
>> + timings = onfi_async_timing_mode_to_sdr_timings(0);
>> + if (IS_ERR(timings))
>> + return PTR_ERR(timings);
>> +
>> + ret = sunxi_nand_chip_set_timings(chip, timings);
>> +
>> + nand = &chip->nand;
>> + nand->controller = &nfc->controller;
>> + nand->select_chip = sunxi_nfc_select_chip;
>> + nand->cmd_ctrl = sunxi_nfc_cmd_ctrl;
>> + nand->read_buf = sunxi_nfc_read_buf;
>> + nand->write_buf = sunxi_nfc_write_buf;
>> + nand->read_byte = sunxi_nfc_read_byte;
>> +
>> + nand->ecc.mode = of_get_nand_ecc_mode(np);
>> + if (of_get_nand_on_flash_bbt(np))
>> + nand->bbt_options |= NAND_BBT_USE_FLASH;
>> +
>> + mtd = &chip->mtd;
>> + mtd->priv = nand;
>> + mtd->owner = THIS_MODULE;
>> +
>> + ret = nand_scan_ident(mtd, nsels, NULL);
>> + if (ret)
>> + return ret;
>> +
>> + ret = sunxi_nand_chip_init_timings(chip, np);
>> + if (ret)
>> + return ret;
>> +
>> + if (nand->ecc.mode == NAND_ECC_SOFT_BCH) {
>> + if (!of_get_nand_ecc_level(np, &strength, &blk_size)) {
>> + nand->ecc_step_ds = blk_size;
>> + nand->ecc_strength_ds = strength;
>> + }
>> +
>> + nand->ecc.size = nand->ecc_step_ds;
>> + nand->ecc.bytes = (((nand->ecc_strength_ds *
>> + fls(8 * nand->ecc_step_ds)) + 7) / 8);
>> + }
>> +
>> + ret = nand_scan_tail(mtd);
>> + if (ret)
>> + return ret;
>> +
>> + if (of_property_read_string(np, "nand-name", &mtd->name)) {
>> + snprintf(chip->default_name, MAX_NAME_SIZE,
>> + DEFAULT_NAME_FORMAT, chip->sels[i].cs);
>> + mtd->name = chip->default_name;
>> + }
>> +
>> + ppdata.of_node = np;
>> + ret = mtd_device_parse_register(mtd, NULL, &ppdata, NULL, 0);
>> + if (!ret)
>> + return ret;
>> +
>> + list_add_tail(&chip->node, &nfc->chips);
>> +
>> + return 0;
>> +}
>> +
>> +static int sunxi_nand_chips_init(struct device *dev, struct sunxi_nfc *nfc)
>> +{
>> + struct device_node *np = dev->of_node;
>> + struct device_node *nand_np;
>> + int nchips = of_get_child_count(np);
>> + int ret;
>> +
>> + if (nchips > 8)
>> + return -EINVAL;
>> +
>> + for_each_child_of_node(np, nand_np) {
>> + ret = sunxi_nand_chip_init(dev, nfc, nand_np);
>> + if (ret)
>> + return ret;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +static int sunxi_nfc_probe(struct platform_device *pdev)
>> +{
>> + struct device *dev = &pdev->dev;
>> + struct resource *r;
>> + struct sunxi_nfc *nfc;
>> + int ret;
>> +
>> + nfc = devm_kzalloc(dev, sizeof(*nfc), GFP_KERNEL);
>> + if (!nfc) {
>> + dev_err(dev, "failed to allocate NFC struct\n");
>> + return -ENOMEM;
>> + }
>> +
>> + spin_lock_init(&nfc->controller.lock);
>> + init_waitqueue_head(&nfc->controller.wq);
>> + INIT_LIST_HEAD(&nfc->chips);
>> +
>> + r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>> + nfc->regs = devm_ioremap_resource(dev, r);
>> + if (IS_ERR(nfc->regs)) {
>> + dev_err(dev, "failed to remap iomem\n");
>> + return PTR_ERR(nfc->regs);
>> + }
>> +
>> + nfc->irq = platform_get_irq(pdev, 0);
>> + if (nfc->irq < 0) {
>> + dev_err(dev, "failed to retrieve irq\n");
>> + return nfc->irq;
>> + }
>> +
>> + nfc->ahb_clk = devm_clk_get(dev, "ahb_clk");
>> + if (IS_ERR(nfc->ahb_clk)) {
>> + dev_err(dev, "failed to retrieve ahb_clk\n");
>> + return PTR_ERR(nfc->ahb_clk);
>> + }
>> +
>> + ret = clk_prepare_enable(nfc->ahb_clk);
>> + if (ret)
>> + return ret;
>> +
>> + nfc->sclk = devm_clk_get(dev, "sclk");
>> + if (IS_ERR(nfc->sclk)) {
>> + dev_err(dev, "failed to retrieve nand_clk\n");
>> + ret = PTR_ERR(nfc->sclk);
>> + goto out_ahb_clk_unprepare;
>> + }
>> +
>> + ret = clk_prepare_enable(nfc->sclk);
>> + if (ret)
>> + goto out_ahb_clk_unprepare;
>> +
>> + /* Reset NFC */
>> + writel(readl(nfc->regs + NFC_REG_CTL) | NFC_RESET,
>> + nfc->regs + NFC_REG_CTL);
>> + while (readl(nfc->regs + NFC_REG_CTL) & NFC_RESET)
>> + ;
>> +
>> + writel(0, nfc->regs + NFC_REG_INT);
>> + ret = devm_request_irq(dev, nfc->irq, sunxi_nfc_interrupt,
>> + 0, "sunxi-nand", nfc);
>> + if (ret)
>> + goto out_sclk_unprepare;
>> +
>> + platform_set_drvdata(pdev, nfc);
>> +
>> + writel(0x100, nfc->regs + NFC_REG_TIMING_CTL);
>> + writel(0x7ff, nfc->regs + NFC_REG_TIMING_CFG);
>> +
>> + ret = sunxi_nand_chips_init(dev, nfc);
>> + if (ret) {
>> + dev_err(dev, "failed to init nand chips\n");
>> + goto out_sclk_unprepare;
>> + }
>> +
>> + return 0;
>> +
>> +out_sclk_unprepare:
>> + clk_disable_unprepare(nfc->sclk);
>> +out_ahb_clk_unprepare:
>> + clk_disable_unprepare(nfc->ahb_clk);
>> +
>> + return ret;
>> +}
>> +
>> +static const struct of_device_id sunxi_nfc_ids[] = {
>> + { .compatible = "allwinner,sun4i-nand" },
>> + { /* sentinel */ }
>> +};
>> +MODULE_DEVICE_TABLE(of, sunxi_nfc_ids);
>> +
>> +static struct platform_driver sunxi_nfc_driver = {
>> + .driver = {
>> + .name = "sunxi_nand",
>> + .owner = THIS_MODULE,
>> + .of_match_table = of_match_ptr(sunxi_nfc_ids),
>> + },
>> + .probe = sunxi_nfc_probe,
>> +};
>> +module_platform_driver(sunxi_nfc_driver);
>> +
>> +MODULE_LICENSE("GPL v2");
>> +MODULE_AUTHOR("Boris BREZILLON");
>> +MODULE_DESCRIPTION("Allwinner NAND Flash Controller driver");
>> +MODULE_ALIAS("platform:sunxi_nfc");
>> --
>> 1.7.9.5
>>
On Wed, 29 Jan 2014 14:53:32 -0300, Ezequiel Garcia <[email protected]> wrote:
> On Wed, Jan 29, 2014 at 03:34:13PM +0100, Boris BREZILLON wrote:
> > nand-ecc-level property statically defines NAND chip's ECC requirements.
> >
> > Signed-off-by: Boris BREZILLON <[email protected]>
> > ---
> > Documentation/devicetree/bindings/mtd/nand.txt | 3 +++
> > 1 file changed, 3 insertions(+)
> >
> > diff --git a/Documentation/devicetree/bindings/mtd/nand.txt b/Documentation/devicetree/bindings/mtd/nand.txt
> > index 03855c8..0c962296 100644
> > --- a/Documentation/devicetree/bindings/mtd/nand.txt
> > +++ b/Documentation/devicetree/bindings/mtd/nand.txt
> > @@ -3,5 +3,8 @@
> > - nand-ecc-mode : String, operation mode of the NAND ecc mode.
> > Supported values are: "none", "soft", "hw", "hw_syndrome", "hw_oob_first",
> > "soft_bch".
> > +- nand-ecc-level : Two cells property defining the ECC level requirements.
> > + The first cell represent the strength and the second cell the ECC block size.
> > + E.g. : nand-ecc-level = <4 512>; /* 4 bits / 512 bytes */
> > - nand-bus-width : 8 or 16 bus width if not present 8
> > - nand-on-flash-bbt: boolean to enable on flash bbt option if not present false
>
> Hm.. when was this proposal agreed? It seems I've missed the
> discussion...
>
> FWIW, we've already proposed an equivalent one, but it received no
> feedback from the devicetree maintainers:
Sorry, binding review has become a huge undertaking.
>
> http://comments.gmane.org/gmane.linux.drivers.devicetree/58764
>
> Maybe we can discuss about it now?
>
> nand-ecc-strength : integer ECC required strength.
> nand-ecc-size : integer step size associated to the ECC strength.
I'm okay with either, but the above binding is indeed more readable.
g.
On 05/02/2014 12:15, Grant Likely wrote:
> On Wed, 29 Jan 2014 14:53:32 -0300, Ezequiel Garcia <[email protected]> wrote:
>> On Wed, Jan 29, 2014 at 03:34:13PM +0100, Boris BREZILLON wrote:
>>> nand-ecc-level property statically defines NAND chip's ECC requirements.
>>>
>>> Signed-off-by: Boris BREZILLON <[email protected]>
>>> ---
>>> Documentation/devicetree/bindings/mtd/nand.txt | 3 +++
>>> 1 file changed, 3 insertions(+)
>>>
>>> diff --git a/Documentation/devicetree/bindings/mtd/nand.txt b/Documentation/devicetree/bindings/mtd/nand.txt
>>> index 03855c8..0c962296 100644
>>> --- a/Documentation/devicetree/bindings/mtd/nand.txt
>>> +++ b/Documentation/devicetree/bindings/mtd/nand.txt
>>> @@ -3,5 +3,8 @@
>>> - nand-ecc-mode : String, operation mode of the NAND ecc mode.
>>> Supported values are: "none", "soft", "hw", "hw_syndrome", "hw_oob_first",
>>> "soft_bch".
>>> +- nand-ecc-level : Two cells property defining the ECC level requirements.
>>> + The first cell represent the strength and the second cell the ECC block size.
>>> + E.g. : nand-ecc-level = <4 512>; /* 4 bits / 512 bytes */
>>> - nand-bus-width : 8 or 16 bus width if not present 8
>>> - nand-on-flash-bbt: boolean to enable on flash bbt option if not present false
>> Hm.. when was this proposal agreed? It seems I've missed the
>> discussion...
>>
>> FWIW, we've already proposed an equivalent one, but it received no
>> feedback from the devicetree maintainers:
> Sorry, binding review has become a huge undertaking.
>
>> http://comments.gmane.org/gmane.linux.drivers.devicetree/58764
>>
>> Maybe we can discuss about it now?
>>
>> nand-ecc-strength : integer ECC required strength.
>> nand-ecc-size : integer step size associated to the ECC strength.
> I'm okay with either, but the above binding is indeed more readable.
That's fine by me, if everybody agrees, let's go for the
nand-ecc-strength/nand-ecc-size couple then.
I'll rebase next version of my series on Ezequiel's patch providing
these OF helpers.
Best Regards,
Boris
>
> g.
Hi Grant, Boris:
(BTW, dropped Russell, Rob Landley and some unrelated mailing lists from Cc,
and added Thomas, Gregory and Rob Herring).
On Wed, Feb 05, 2014 at 02:34:44PM +0100, Boris BREZILLON wrote:
> On 05/02/2014 12:15, Grant Likely wrote:
> > On Wed, 29 Jan 2014 14:53:32 -0300, Ezequiel Garcia <[email protected]> wrote:
[..]
> >>
> >> Maybe we can discuss about it now?
> >>
> >> nand-ecc-strength : integer ECC required strength.
> >> nand-ecc-size : integer step size associated to the ECC strength.
> > I'm okay with either, but the above binding is indeed more readable.
>
> That's fine by me, if everybody agrees, let's go for the
> nand-ecc-strength/nand-ecc-size couple then.
>
Great. So, if some DT dictator^C^Cmaintainers can Ack this binding,
I can send a new patchset, with pxa3xx-nand using it...
> I'll rebase next version of my series on Ezequiel's patch providing
> these OF helpers.
>
... and then you can base on it?
This is the original patchset:
http://permalink.gmane.org/gmane.linux.drivers.devicetree/58764
http://permalink.gmane.org/gmane.linux.drivers.devicetree/58763
--
Ezequiel García, Free Electrons
Embedded Linux, Kernel and Android Engineering
http://free-electrons.com
On Wed, Feb 5, 2014 at 2:19 PM, Ezequiel Garcia
<[email protected]> wrote:
> Hi Grant, Boris:
>
> (BTW, dropped Russell, Rob Landley and some unrelated mailing lists from Cc,
> and added Thomas, Gregory and Rob Herring).
>
> On Wed, Feb 05, 2014 at 02:34:44PM +0100, Boris BREZILLON wrote:
>> On 05/02/2014 12:15, Grant Likely wrote:
>> > On Wed, 29 Jan 2014 14:53:32 -0300, Ezequiel Garcia <[email protected]> wrote:
> [..]
>> >>
>> >> Maybe we can discuss about it now?
>> >>
>> >> nand-ecc-strength : integer ECC required strength.
>> >> nand-ecc-size : integer step size associated to the ECC strength.
>> > I'm okay with either, but the above binding is indeed more readable.
>>
>> That's fine by me, if everybody agrees, let's go for the
>> nand-ecc-strength/nand-ecc-size couple then.
>>
>
> Great. So, if some DT dictator^C^Cmaintainers can Ack this binding,
> I can send a new patchset, with pxa3xx-nand using it...
>
>> I'll rebase next version of my series on Ezequiel's patch providing
>> these OF helpers.
>>
>
> ... and then you can base on it?
>
> This is the original patchset:
>
> http://permalink.gmane.org/gmane.linux.drivers.devicetree/58764
> http://permalink.gmane.org/gmane.linux.drivers.devicetree/58763
I've looked at both. Go ahead and add my a-b line:
Acked-by: Grant Likely <[email protected]>
On Mon, Feb 17, 2014 at 04:43:51PM +0000, Grant Likely wrote:
> On Wed, Feb 5, 2014 at 2:19 PM, Ezequiel Garcia
> <[email protected]> wrote:
> > Hi Grant, Boris:
> >
> > (BTW, dropped Russell, Rob Landley and some unrelated mailing lists from Cc,
> > and added Thomas, Gregory and Rob Herring).
> >
> > On Wed, Feb 05, 2014 at 02:34:44PM +0100, Boris BREZILLON wrote:
> >> On 05/02/2014 12:15, Grant Likely wrote:
> >> > On Wed, 29 Jan 2014 14:53:32 -0300, Ezequiel Garcia <[email protected]> wrote:
> > [..]
> >> >>
> >> >> Maybe we can discuss about it now?
> >> >>
> >> >> nand-ecc-strength : integer ECC required strength.
> >> >> nand-ecc-size : integer step size associated to the ECC strength.
> >> > I'm okay with either, but the above binding is indeed more readable.
> >>
> >> That's fine by me, if everybody agrees, let's go for the
> >> nand-ecc-strength/nand-ecc-size couple then.
> >>
> >
> > Great. So, if some DT dictator^C^Cmaintainers can Ack this binding,
> > I can send a new patchset, with pxa3xx-nand using it...
> >
> >> I'll rebase next version of my series on Ezequiel's patch providing
> >> these OF helpers.
> >>
> >
> > ... and then you can base on it?
> >
> > This is the original patchset:
> >
> > http://permalink.gmane.org/gmane.linux.drivers.devicetree/58764
> > http://permalink.gmane.org/gmane.linux.drivers.devicetree/58763
>
> I've looked at both. Go ahead and add my a-b line:
>
> Acked-by: Grant Likely <[email protected]>
Cool. Thanks!
I'll push a patchset now.
--
Ezequiel García, Free Electrons
Embedded Linux, Kernel and Android Engineering
http://free-electrons.com