aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Rini2023-01-06 11:53:26 -0500
committerTom Rini2023-01-06 11:53:26 -0500
commitb82f12b6426e7a5f8504759b3bcf3906897d7f6c (patch)
tree1cd6a8de2f9d107ead8193262c6eba71c3a540d2
parentb63905cfc610542ebb712e841ae474b06e056146 (diff)
parent61040097a9d1c8022bae7936c52b729ff1adb163 (diff)
Merge tag 'u-boot-at91-2023.04-a' of https://source.denx.de/u-boot/custodians/u-boot-at91 into next
First set of u-boot-at91 features for the 2023.04 cycle: This feature set includes the new DM-based NAND flash driver (old non-DM driver is still kept for backwards compatibility), and the move to DM NAND flash driver for sam9x60ek board. Feature set also includes devicetree alignment for sama7g5 with Linux, devicetree alignment on USB with Linux for all boards (sama5, sam9x60), chip id for sama7g5, minor configs and tweaks.
-rw-r--r--MAINTAINERS1
-rw-r--r--arch/arm/dts/at91-sam9x60_curiosity.dts21
-rw-r--r--arch/arm/dts/at91-sama5d27_wlsom1_ek.dts25
-rw-r--r--arch/arm/dts/at91-sama5d2_icp.dts22
-rw-r--r--arch/arm/dts/at91-sama7g5ek-u-boot.dtsi108
-rw-r--r--arch/arm/dts/at91-sama7g5ek.dts23
-rw-r--r--arch/arm/dts/sam9x60.dtsi85
-rw-r--r--arch/arm/dts/sam9x60ek.dts124
-rw-r--r--arch/arm/dts/sama5d2.dtsi1
-rw-r--r--arch/arm/dts/sama7g5.dtsi27
-rw-r--r--arch/arm/mach-at91/armv7/sama7g5_devices.c26
-rw-r--r--arch/arm/mach-at91/include/mach/sama7-sfr.h59
-rw-r--r--arch/arm/mach-at91/include/mach/sama7g5.h28
-rw-r--r--board/atmel/sam9x60_curiosity/MAINTAINERS1
-rw-r--r--board/atmel/sam9x60_curiosity/sam9x60_curiosity.c10
-rw-r--r--board/atmel/sam9x60ek/sam9x60ek.c62
-rw-r--r--configs/sam9x60_curiosity_mmc1_defconfig92
-rw-r--r--configs/sam9x60ek_mmc_defconfig9
-rw-r--r--configs/sam9x60ek_nandflash_defconfig9
-rw-r--r--configs/sam9x60ek_qspiflash_defconfig8
-rw-r--r--drivers/Kconfig2
-rw-r--r--drivers/Makefile1
-rw-r--r--drivers/memory/Kconfig7
-rw-r--r--drivers/memory/Makefile1
-rw-r--r--drivers/memory/atmel_ebi.c37
-rw-r--r--drivers/mfd/Kconfig4
-rw-r--r--drivers/mfd/Makefile1
-rw-r--r--drivers/mfd/atmel-smc.c364
-rw-r--r--drivers/mtd/nand/raw/Kconfig8
-rw-r--r--drivers/mtd/nand/raw/Makefile1
-rw-r--r--drivers/mtd/nand/raw/atmel/Makefile5
-rw-r--r--drivers/mtd/nand/raw/atmel/nand-controller.c2286
-rw-r--r--drivers/mtd/nand/raw/atmel/pmecc.c965
-rw-r--r--drivers/mtd/nand/raw/atmel/pmecc.h94
-rw-r--r--drivers/reset/Kconfig8
-rw-r--r--drivers/reset/Makefile1
-rw-r--r--drivers/reset/reset-at91.c141
-rw-r--r--drivers/sysreset/sysreset_at91.c9
-rw-r--r--include/configs/sam9x60ek.h9
-rw-r--r--include/dt-bindings/clk/at91.h5
-rw-r--r--include/dt-bindings/mfd/at91-usart.h17
-rw-r--r--include/dt-bindings/reset/sama7g5-reset.h10
-rw-r--r--include/linux/mfd/syscon/atmel-matrix.h112
-rw-r--r--include/linux/mfd/syscon/atmel-smc.h119
44 files changed, 4841 insertions, 107 deletions
diff --git a/MAINTAINERS b/MAINTAINERS
index 5f9636f4e18..237f3940b43 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -409,6 +409,7 @@ T: git https://source.denx.de/u-boot/custodians/u-boot-atmel.git
F: arch/arm/mach-at91/
F: board/atmel/
F: drivers/cpu/at91_cpu.c
+F: drivers/memory/atmel-ebi.c
F: drivers/misc/microchip_flexcom.c
F: drivers/timer/atmel_tcb_timer.c
F: include/dt-bindings/mfd/atmel-flexcom.h
diff --git a/arch/arm/dts/at91-sam9x60_curiosity.dts b/arch/arm/dts/at91-sam9x60_curiosity.dts
index 7c5b6ae2b87..d6ae3d648da 100644
--- a/arch/arm/dts/at91-sam9x60_curiosity.dts
+++ b/arch/arm/dts/at91-sam9x60_curiosity.dts
@@ -49,6 +49,13 @@
atmel,pins =
<AT91_PIOD 14 AT91_PERIPH_GPIO AT91_PINCTRL_PULL_UP>;
};
+
+ usb1 {
+ pinctrl_usb_default: usb_default {
+ atmel,pins = <AT91_PIOD 15 AT91_PERIPH_GPIO AT91_PINCTRL_NONE
+ AT91_PIOD 18 AT91_PERIPH_GPIO AT91_PINCTRL_NONE>;
+ };
+ };
};
};
};
@@ -89,3 +96,17 @@
phy-mode = "rmii";
status = "okay";
};
+
+&usb1 {
+ num-ports = <3>;
+ atmel,vbus-gpio = <0
+ &pioD 15 GPIO_ACTIVE_HIGH
+ &pioD 18 GPIO_ACTIVE_HIGH>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_usb_default>;
+ status = "okay";
+};
+
+&usb2 {
+ status = "okay";
+};
diff --git a/arch/arm/dts/at91-sama5d27_wlsom1_ek.dts b/arch/arm/dts/at91-sama5d27_wlsom1_ek.dts
index eec183d5de7..6d4b35ea966 100644
--- a/arch/arm/dts/at91-sama5d27_wlsom1_ek.dts
+++ b/arch/arm/dts/at91-sama5d27_wlsom1_ek.dts
@@ -143,7 +143,32 @@
pinmux = <PIN_PC9__GPIO>;
bias-pull-up;
};
+
+ pinctrl_usb_default: usb_default {
+ pinmux = <PIN_PA10__GPIO>;
+ bias-disable;
+ };
+
+ pinctrl_usba_vbus: usba_vbus {
+ pinmux = <PIN_PA16__GPIO>;
+ bias-disable;
+ };
};
};
};
};
+
+&usb1 {
+ num-ports = <3>;
+ atmel,vbus-gpio = <0
+ &pioA PIN_PA10 GPIO_ACTIVE_HIGH
+ 0
+ >;
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_usb_default>;
+ status = "okay";
+};
+
+&usb2 {
+ status = "okay";
+};
diff --git a/arch/arm/dts/at91-sama5d2_icp.dts b/arch/arm/dts/at91-sama5d2_icp.dts
index 2dffae9c5ca..4f796c6c949 100644
--- a/arch/arm/dts/at91-sama5d2_icp.dts
+++ b/arch/arm/dts/at91-sama5d2_icp.dts
@@ -154,7 +154,29 @@
<PIN_PA13__SDMMC0_CD>;
bias-disable;
};
+
+ pinctrl_usb_default: usb_default {
+ pinmux = <PIN_PC17__GPIO>;
+ bias-disable;
+ };
+
+ pinctrl_usba_vbus: usba_vbus {
+ pinmux = <PIN_PD23__GPIO>;
+ bias-disable;
+ };
};
};
};
};
+
+&usb1 {
+ num-ports = <3>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_usb_default>;
+ status = "okay";
+};
+
+&usb2 {
+ phy_type = "hsic";
+ status = "okay";
+};
diff --git a/arch/arm/dts/at91-sama7g5ek-u-boot.dtsi b/arch/arm/dts/at91-sama7g5ek-u-boot.dtsi
index d294ddb54ae..a54cfaccbf6 100644
--- a/arch/arm/dts/at91-sama7g5ek-u-boot.dtsi
+++ b/arch/arm/dts/at91-sama7g5ek-u-boot.dtsi
@@ -10,13 +10,88 @@
*
*/
+#include "sama7g5-pinfunc.h"
+#include <dt-bindings/reset/sama7g5-reset.h>
+#include <dt-bindings/clock/at91.h>
+
/ {
chosen {
u-boot,dm-pre-reloc;
};
+ utmi {
+ compatible = "simple-bus";
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ usb_phy0: phy@0 {
+ compatible = "microchip,sama7g5-usb-phy";
+ sfr-phandle = <&sfr>;
+ reg = <0>;
+ clocks = <&utmi_clk USB_UTMI1>;
+ clock-names = "utmi_clk";
+ status = "disabled";
+ #phy-cells = <0>;
+ };
+
+ usb_phy1: phy@1 {
+ compatible = "microchip,sama7g5-usb-phy";
+ sfr-phandle = <&sfr>;
+ reg = <1>;
+ clocks = <&utmi_clk USB_UTMI2>;
+ clock-names = "utmi_clk";
+ status = "disabled";
+ #phy-cells = <0>;
+ };
+
+ usb_phy2: phy@2 {
+ compatible = "microchip,sama7g5-usb-phy";
+ sfr-phandle = <&sfr>;
+ reg = <2>;
+ clocks = <&utmi_clk USB_UTMI3>;
+ clock-names = "utmi_clk";
+ status = "disabled";
+ #phy-cells = <0>;
+ };
+ };
+
+ utmi_clk: utmi-clk {
+ compatible = "microchip,sama7g5-utmi-clk";
+ sfr-phandle = <&sfr>;
+ #clock-cells = <1>;
+ clocks = <&pmc PMC_TYPE_CORE 27>;
+ clock-names = "utmi_clk";
+ resets = <&reset_controller SAMA7G5_RESET_USB_PHY1>,
+ <&reset_controller SAMA7G5_RESET_USB_PHY2>,
+ <&reset_controller SAMA7G5_RESET_USB_PHY3>;
+ reset-names = "usb0_reset", "usb1_reset", "usb2_reset";
+ };
+
soc {
u-boot,dm-pre-reloc;
+
+ usb2: usb@400000 {
+ compatible = "microchip,sama7g5-ohci", "usb-ohci";
+ reg = <0x00400000 0x100000>;
+ interrupts = <GIC_SPI 106 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&pmc PMC_TYPE_PERIPHERAL 106>, <&utmi_clk USB_UTMI1>, <&usb_clk>;
+ clock-names = "ohci_clk", "hclk", "uhpck";
+ status = "disabled";
+ };
+
+ usb3: usb@500000 {
+ compatible = "atmel,at91sam9g45-ehci", "usb-ehci";
+ reg = <0x00500000 0x100000>;
+ interrupts = <GIC_SPI 106 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&usb_clk>, <&pmc PMC_TYPE_PERIPHERAL 106>;
+ clock-names = "usb_clk", "ehci_clk";
+ status = "disabled";
+ };
+
+ sfr: sfr@e1624000 {
+ compatible = "microchip,sama7g5-sfr", "syscon";
+ reg = <0xe1624000 0x4000>;
+ };
};
};
@@ -38,6 +113,11 @@
&pioA {
u-boot,dm-pre-reloc;
+
+ pinctrl_usb_default: usb_default {
+ pinmux = <PIN_PC6__GPIO>;
+ bias-disable;
+ };
};
&pit64b0 {
@@ -60,3 +140,31 @@
u-boot,dm-pre-reloc;
};
+&usb2 {
+ num-ports = <3>;
+ atmel,vbus-gpio = <0
+ 0
+ &pioA PIN_PC6 GPIO_ACTIVE_HIGH
+ >;
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_usb_default>;
+ phys = <&usb_phy2>;
+ phy-names = "usb";
+ status = "okay";
+};
+
+&usb3 {
+ status = "okay";
+};
+
+&usb_phy0 {
+ status = "okay";
+};
+
+&usb_phy1 {
+ status = "okay";
+};
+
+&usb_phy2 {
+ status = "okay";
+};
diff --git a/arch/arm/dts/at91-sama7g5ek.dts b/arch/arm/dts/at91-sama7g5ek.dts
index aed84f15a11..9b247fcaf66 100644
--- a/arch/arm/dts/at91-sama7g5ek.dts
+++ b/arch/arm/dts/at91-sama7g5ek.dts
@@ -45,13 +45,13 @@
};
};
- gpio_keys {
+ gpio-keys {
compatible = "gpio-keys";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_key_gpio_default>;
- bp1 {
+ button {
label = "PB_USER";
gpios = <&pioA PIN_PA12 GPIO_ACTIVE_LOW>;
linux,code = <KEY_PROG1>;
@@ -244,8 +244,8 @@
regulators {
vdd_3v3: VDD_IO {
regulator-name = "VDD_IO";
- regulator-min-microvolt = <1200000>;
- regulator-max-microvolt = <3700000>;
+ regulator-min-microvolt = <3300000>;
+ regulator-max-microvolt = <3300000>;
regulator-initial-mode = <2>;
regulator-allowed-modes = <2>, <4>;
regulator-always-on;
@@ -264,8 +264,8 @@
vddioddr: VDD_DDR {
regulator-name = "VDD_DDR";
- regulator-min-microvolt = <1300000>;
- regulator-max-microvolt = <1450000>;
+ regulator-min-microvolt = <1350000>;
+ regulator-max-microvolt = <1350000>;
regulator-initial-mode = <2>;
regulator-allowed-modes = <2>, <4>;
regulator-always-on;
@@ -285,8 +285,8 @@
vddcore: VDD_CORE {
regulator-name = "VDD_CORE";
- regulator-min-microvolt = <1100000>;
- regulator-max-microvolt = <1850000>;
+ regulator-min-microvolt = <1150000>;
+ regulator-max-microvolt = <1150000>;
regulator-initial-mode = <2>;
regulator-allowed-modes = <2>, <4>;
regulator-always-on;
@@ -306,7 +306,7 @@
vddcpu: VDD_OTHER {
regulator-name = "VDD_OTHER";
regulator-min-microvolt = <1050000>;
- regulator-max-microvolt = <1850000>;
+ regulator-max-microvolt = <1250000>;
regulator-initial-mode = <2>;
regulator-allowed-modes = <2>, <4>;
regulator-ramp-delay = <3125>;
@@ -326,8 +326,8 @@
vldo1: LDO1 {
regulator-name = "LDO1";
- regulator-min-microvolt = <1200000>;
- regulator-max-microvolt = <3700000>;
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <1800000>;
regulator-always-on;
regulator-state-standby {
@@ -707,7 +707,6 @@
ck_cd_rstn_vddsel {
pinmux = <PIN_PA0__SDMMC0_CK>,
<PIN_PA2__SDMMC0_RSTN>,
- <PIN_PA14__SDMMC0_CD>,
<PIN_PA11__SDMMC0_DS>;
slew-rate = <0>;
bias-pull-up;
diff --git a/arch/arm/dts/sam9x60.dtsi b/arch/arm/dts/sam9x60.dtsi
index a5c429eb3a7..2b93d089384 100644
--- a/arch/arm/dts/sam9x60.dtsi
+++ b/arch/arm/dts/sam9x60.dtsi
@@ -69,6 +69,50 @@
#size-cells = <1>;
ranges;
+ usb1: usb@600000 {
+ compatible = "atmel,at91rm9200-ohci", "usb-ohci";
+ reg = <0x00600000 0x100000>;
+ clocks = <&pmc PMC_TYPE_PERIPHERAL 22>, <&pmc PMC_TYPE_PERIPHERAL 22>, <&pmc PMC_TYPE_SYSTEM 21>;
+ clock-names = "ohci_clk", "hclk", "uhpck";
+ status = "disabled";
+ };
+
+ usb2: usb@700000 {
+ compatible = "atmel,at91sam9g45-ehci", "usb-ehci";
+ reg = <0x00700000 0x100000>;
+ clocks = <&pmc PMC_TYPE_CORE 8>, <&pmc PMC_TYPE_PERIPHERAL 22>;
+ clock-names = "usb_clk", "ehci_clk";
+ assigned-clocks = <&pmc PMC_TYPE_CORE 8>;
+ assigned-clock-rates = <480000000>;
+ status = "disabled";
+ };
+
+ ebi: ebi@10000000 {
+ compatible = "microchip,sam9x60-ebi";
+ #address-cells = <2>;
+ #size-cells = <1>;
+ atmel,smc = <&smc>;
+ microchip,sfr = <&sfr>;
+ reg = <0x10000000 0x60000000>;
+ ranges = <0x0 0x0 0x10000000 0x10000000
+ 0x1 0x0 0x20000000 0x10000000
+ 0x2 0x0 0x30000000 0x10000000
+ 0x3 0x0 0x40000000 0x10000000
+ 0x4 0x0 0x50000000 0x10000000
+ 0x5 0x0 0x60000000 0x10000000>;
+ clocks = <&pmc PMC_TYPE_CORE 11>;
+ status = "disabled";
+
+ nand_controller: nand-controller {
+ compatible = "microchip,sam9x60-nand-controller";
+ ecc-engine = <&pmecc>;
+ #address-cells = <2>;
+ #size-cells = <1>;
+ ranges;
+ status = "disabled";
+ };
+ };
+
sdhci0: sdhci-host@80000000 {
compatible = "microchip,sam9x60-sdhci";
reg = <0x80000000 0x300>;
@@ -82,6 +126,19 @@
pinctrl-0 = <&pinctrl_sdhci0>;
};
+ sdhci1: sdhci-host@90000000 {
+ compatible = "microchip,sam9x60-sdhci";
+ reg = <0x90000000 0x300>;
+ clocks = <&pmc PMC_TYPE_PERIPHERAL 26>, <&pmc PMC_TYPE_GCK 26>;
+ clock-names = "hclock", "multclk";
+ assigned-clocks = <&pmc PMC_TYPE_GCK 26>;
+ assigned-clock-rates = <100000000>;
+ assigned-clock-parents = <&pmc PMC_TYPE_CORE 10>; /* ID_PLL_A_DIV */
+ bus-width = <4>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_sdhci1>;
+ };
+
apb {
compatible = "simple-bus";
#address-cells = <1>;
@@ -119,6 +176,11 @@
status = "disabled";
};
+ sfr: sfr@f8050000 {
+ compatible = "microchip,sam9x60-sfr", "syscon";
+ reg = <0xf8050000 0x100>;
+ };
+
dbgu: serial@fffff200 {
compatible = "atmel,at91sam9260-dbgu", "atmel,at91sam9260-usart";
reg = <0xfffff200 0x200>;
@@ -180,6 +242,29 @@
(AT91_PINCTRL_PULL_UP | AT91_PINCTRL_DRIVE_STRENGTH_HI | AT91_PINCTRL_SLEWRATE_ENA)>; /* PA20 DAT3 periph A with pullup */
};
};
+
+ sdhci1 {
+ pinctrl_sdhci1: sdhci1 {
+ atmel,pins =
+ <AT91_PIOA 13 AT91_PERIPH_B (AT91_PINCTRL_DRIVE_STRENGTH_HI) /* PA13 CK periph B */
+ AT91_PIOA 12 AT91_PERIPH_B (AT91_PINCTRL_PULL_UP | AT91_PINCTRL_DRIVE_STRENGTH_HI) /* PA12 CMD periph B with pullup */
+ AT91_PIOA 11 AT91_PERIPH_B (AT91_PINCTRL_PULL_UP | AT91_PINCTRL_DRIVE_STRENGTH_HI) /* PA11 DAT0 periph B with pullup */
+ AT91_PIOA 2 AT91_PERIPH_B (AT91_PINCTRL_PULL_UP | AT91_PINCTRL_DRIVE_STRENGTH_HI) /* PA2 DAT1 periph B with pullup */
+ AT91_PIOA 3 AT91_PERIPH_B (AT91_PINCTRL_PULL_UP | AT91_PINCTRL_DRIVE_STRENGTH_HI) /* PA3 DAT2 periph B with pullup */
+ AT91_PIOA 4 AT91_PERIPH_B (AT91_PINCTRL_PULL_UP | AT91_PINCTRL_DRIVE_STRENGTH_HI)>; /* PA4 DAT3 periph B with pullup */
+ };
+ };
+ };
+
+ pmecc: ecc-engine@ffffe000 {
+ compatible = "microchip,sam9x60-pmecc", "atmel,at91sam9g45-pmecc";
+ reg = <0xffffe000 0x300>,
+ <0xffffe600 0x100>;
+ };
+
+ smc: smc@ffffea00 {
+ compatible = "microchip,sam9x60-smc", "atmel,at91sam9260-smc", "syscon";
+ reg = <0xffffea00 0x100>;
};
pioA: gpio@fffff400 {
diff --git a/arch/arm/dts/sam9x60ek.dts b/arch/arm/dts/sam9x60ek.dts
index eb44868a3e3..45e2f4cc406 100644
--- a/arch/arm/dts/sam9x60ek.dts
+++ b/arch/arm/dts/sam9x60ek.dts
@@ -80,6 +80,44 @@
};
pinctrl {
+ nand {
+ pinctrl_nand_oe_we: nand-oe-we-0 {
+ atmel,pins =
+ <AT91_PIOD 0 AT91_PERIPH_A (AT91_PINCTRL_NONE | AT91_PINCTRL_SLEWRATE_DIS)
+ AT91_PIOD 1 AT91_PERIPH_A (AT91_PINCTRL_NONE | AT91_PINCTRL_SLEWRATE_DIS)>;
+ };
+
+ pinctrl_nand_rb: nand-rb-0 {
+ atmel,pins =
+ <AT91_PIOD 5 AT91_PERIPH_GPIO AT91_PINCTRL_PULL_UP>;
+ };
+
+ pinctrl_nand_cs: nand-cs-0 {
+ atmel,pins =
+ <AT91_PIOD 4 AT91_PERIPH_GPIO AT91_PINCTRL_PULL_UP>;
+ };
+ };
+
+ ebi {
+ pinctrl_ebi_data_0_7: ebi-data-lsb-0 {
+ atmel,pins =
+ <AT91_PIOD 6 AT91_PERIPH_A (AT91_PINCTRL_NONE | AT91_PINCTRL_SLEWRATE_DIS)
+ AT91_PIOD 7 AT91_PERIPH_A (AT91_PINCTRL_NONE | AT91_PINCTRL_SLEWRATE_DIS)
+ AT91_PIOD 8 AT91_PERIPH_A (AT91_PINCTRL_NONE | AT91_PINCTRL_SLEWRATE_DIS)
+ AT91_PIOD 9 AT91_PERIPH_A (AT91_PINCTRL_NONE | AT91_PINCTRL_SLEWRATE_DIS)
+ AT91_PIOD 10 AT91_PERIPH_A (AT91_PINCTRL_NONE | AT91_PINCTRL_SLEWRATE_DIS)
+ AT91_PIOD 11 AT91_PERIPH_A (AT91_PINCTRL_NONE | AT91_PINCTRL_SLEWRATE_DIS)
+ AT91_PIOD 12 AT91_PERIPH_A (AT91_PINCTRL_NONE | AT91_PINCTRL_SLEWRATE_DIS)
+ AT91_PIOD 13 AT91_PERIPH_A (AT91_PINCTRL_NONE | AT91_PINCTRL_SLEWRATE_DIS)>;
+ };
+
+ pinctrl_ebi_addr_nand: ebi-addr-0 {
+ atmel,pins =
+ <AT91_PIOD 2 AT91_PERIPH_A (AT91_PINCTRL_NONE | AT91_PINCTRL_SLEWRATE_DIS)
+ AT91_PIOD 3 AT91_PERIPH_A (AT91_PINCTRL_NONE | AT91_PINCTRL_SLEWRATE_DIS)>;
+ };
+ };
+
pinctrl_qspi: qspi {
atmel,pins =
<AT91_PIOB 19 AT91_PERIPH_A AT91_PINCTRL_NONE
@@ -101,6 +139,78 @@
<AT91_PIOD 14 AT91_PERIPH_GPIO AT91_PINCTRL_PULL_UP>;
};
+ usb1 {
+ pinctrl_usb_default: usb_default {
+ atmel,pins = <AT91_PIOD 15 AT91_PERIPH_GPIO AT91_PINCTRL_NONE
+ AT91_PIOD 16 AT91_PERIPH_GPIO AT91_PINCTRL_NONE>;
+ };
+ };
+
+ };
+ };
+ };
+};
+
+&ebi {
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_ebi_addr_nand &pinctrl_ebi_data_0_7>;
+ status = "okay";
+
+ nand_controller: nand-controller {
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_nand_oe_we &pinctrl_nand_cs &pinctrl_nand_rb>;
+ status = "okay";
+
+ nand@3 {
+ reg = <0x3 0x0 0x800000>;
+ rb-gpios = <&pioD 5 GPIO_ACTIVE_HIGH>;
+ cs-gpios = <&pioD 4 GPIO_ACTIVE_HIGH>;
+ nand-bus-width = <8>;
+ nand-ecc-mode = "hw";
+ nand-ecc-strength = <8>;
+ nand-ecc-step-size = <512>;
+ nand-on-flash-bbt;
+ label = "atmel_nand";
+
+ partitions {
+ compatible = "fixed-partitions";
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ at91bootstrap@0 {
+ label = "at91bootstrap";
+ reg = <0x0 0x40000>;
+ };
+
+ uboot@40000 {
+ label = "u-boot";
+ reg = <0x40000 0xc0000>;
+ };
+
+ ubootenvred@100000 {
+ label = "U-Boot Env Redundant";
+ reg = <0x100000 0x40000>;
+ };
+
+ ubootenv@140000 {
+ label = "U-Boot Env";
+ reg = <0x140000 0x40000>;
+ };
+
+ dtb@180000 {
+ label = "device tree";
+ reg = <0x180000 0x80000>;
+ };
+
+ kernel@200000 {
+ label = "kernel";
+ reg = <0x200000 0x600000>;
+ };
+
+ rootfs@800000 {
+ label = "rootfs";
+ reg = <0x800000 0x1f800000>;
+ };
};
};
};
@@ -110,3 +220,17 @@
phy-mode = "rmii";
status = "okay";
};
+
+&usb1 {
+ num-ports = <3>;
+ atmel,vbus-gpio = <0
+ &pioD 15 GPIO_ACTIVE_HIGH
+ &pioD 16 GPIO_ACTIVE_HIGH>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_usb_default>;
+ status = "okay";
+};
+
+&usb2 {
+ status = "okay";
+};
diff --git a/arch/arm/dts/sama5d2.dtsi b/arch/arm/dts/sama5d2.dtsi
index 790b746ed1a..187c2ff2fbd 100644
--- a/arch/arm/dts/sama5d2.dtsi
+++ b/arch/arm/dts/sama5d2.dtsi
@@ -84,7 +84,6 @@
reg = <0xf0014000 0x160>;
#address-cells = <1>;
#size-cells = <0>;
- #interrupt-cells = <1>;
u-boot,dm-pre-reloc;
main: mainck {
diff --git a/arch/arm/dts/sama7g5.dtsi b/arch/arm/dts/sama7g5.dtsi
index d38090d7ddc..6388a60e53b 100644
--- a/arch/arm/dts/sama7g5.dtsi
+++ b/arch/arm/dts/sama7g5.dtsi
@@ -15,6 +15,7 @@
#include <dt-bindings/clk/at91.h>
#include <dt-bindings/dma/at91.h>
#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/mfd/at91-usart.h>
/ {
model = "Microchip SAMA7G5 family SoC";
@@ -195,11 +196,11 @@
<GIC_SPI 13 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 14 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 15 IRQ_TYPE_LEVEL_HIGH>;
- clocks = <&pmc PMC_TYPE_PERIPHERAL 11>;
interrupt-controller;
#interrupt-cells = <2>;
gpio-controller;
#gpio-cells = <2>;
+ clocks = <&pmc PMC_TYPE_PERIPHERAL 11>;
};
pmc: pmc@e0018000 {
@@ -211,6 +212,13 @@
clock-names = "td_slck", "md_slck", "main_xtal", "main_rc";
};
+ reset_controller: reset-controller@e001d000 {
+ compatible = "microchip,sama7g5-rstc";
+ reg = <0xe001d000 0xc>, <0xe001d0e4 0x4>;
+ #reset-cells = <1>;
+ clocks = <&clk32k 0>;
+ };
+
shdwc: shdwc@e001d010 {
compatible = "microchip,sama7g5-shdwc", "syscon";
reg = <0xe001d010 0x10>;
@@ -229,13 +237,6 @@
clocks = <&clk32k 0>;
};
- reset_controller: rstc@e001d000 {
- compatible = "microchip,sama7g5-rstc", "microchip,sam9x60-rstc";
- reg = <0xe001d000 0xc>, <0xe001d0e4 0x4>;
- #reset-cells = <1>;
- clocks = <&clk32k 0>;
- };
-
clk32k: clock-controller@e001d050 {
compatible = "microchip,sama7g5-sckc", "microchip,sam9x60-sckc";
reg = <0xe001d050 0x4>;
@@ -620,6 +621,7 @@
uart0: serial@200 {
compatible = "atmel,at91sam9260-usart";
reg = <0x200 0x200>;
+ atmel,usart-mode = <AT91_USART_MODE_SERIAL>;
interrupts = <GIC_SPI 38 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&pmc PMC_TYPE_PERIPHERAL 38>;
clock-names = "usart";
@@ -668,6 +670,7 @@
uart3: serial@200 {
compatible = "atmel,at91sam9260-usart";
reg = <0x200 0x200>;
+ atmel,usart-mode = <AT91_USART_MODE_SERIAL>;
interrupts = <GIC_SPI 41 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&pmc PMC_TYPE_PERIPHERAL 41>;
clock-names = "usart";
@@ -711,6 +714,7 @@
uart4: serial@200 {
compatible = "atmel,at91sam9260-usart";
reg = <0x200 0x200>;
+ atmel,usart-mode = <AT91_USART_MODE_SERIAL>;
interrupts = <GIC_SPI 42 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&pmc PMC_TYPE_PERIPHERAL 42>;
clock-names = "usart";
@@ -736,6 +740,7 @@
uart7: serial@200 {
compatible = "atmel,at91sam9260-usart";
reg = <0x200 0x200>;
+ atmel,usart-mode = <AT91_USART_MODE_SERIAL>;
interrupts = <GIC_SPI 45 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&pmc PMC_TYPE_PERIPHERAL 45>;
clock-names = "usart";
@@ -884,9 +889,9 @@
#address-cells = <1>;
#size-cells = <0>;
atmel,fifo-size = <32>;
- dmas = <&dma0 AT91_XDMAC_DT_PERID(27)>,
- <&dma0 AT91_XDMAC_DT_PERID(28)>;
- dma-names = "rx", "tx";
+ dmas = <&dma0 AT91_XDMAC_DT_PERID(28)>,
+ <&dma0 AT91_XDMAC_DT_PERID(27)>;
+ dma-names = "tx", "rx";
status = "disabled";
};
};
diff --git a/arch/arm/mach-at91/armv7/sama7g5_devices.c b/arch/arm/mach-at91/armv7/sama7g5_devices.c
index 0b702c7fb76..6f2c1fc9142 100644
--- a/arch/arm/mach-at91/armv7/sama7g5_devices.c
+++ b/arch/arm/mach-at91/armv7/sama7g5_devices.c
@@ -4,7 +4,31 @@
* Eugen Hristev <eugen.hristev@microchip.com>
*/
+#include <asm/arch/sama7g5.h>
+
char *get_cpu_name(void)
{
- return "SAMA7G5";
+ unsigned int extension_id = get_extension_chip_id();
+
+ if (cpu_is_sama7g5())
+ switch (extension_id) {
+ case ARCH_EXID_SAMA7G51:
+ return "SAMA7G51";
+ case ARCH_EXID_SAMA7G52:
+ return "SAMA7G52";
+ case ARCH_EXID_SAMA7G53:
+ return "SAMA7G53";
+ case ARCH_EXID_SAMA7G54:
+ return "SAMA7G54";
+ case ARCH_EXID_SAMA7G54_D1G:
+ return "SAMA7G54 1Gb DDR3L SiP";
+ case ARCH_EXID_SAMA7G54_D2G:
+ return "SAMA7G54 2Gb DDR3L SiP";
+ case ARCH_EXID_SAMA7G54_D4G:
+ return "SAMA7G54 4Gb DDR3L SiP";
+ default:
+ return "Unknown CPU type";
+ }
+ else
+ return "Unknown CPU type";
}
diff --git a/arch/arm/mach-at91/include/mach/sama7-sfr.h b/arch/arm/mach-at91/include/mach/sama7-sfr.h
new file mode 100644
index 00000000000..a987ff54652
--- /dev/null
+++ b/arch/arm/mach-at91/include/mach/sama7-sfr.h
@@ -0,0 +1,59 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Microchip SFR (Special Function Registers) registers for SAMA7 family.
+ *
+ * Copyright (C) 2022 Microchip Technology Inc. and its subsidiaries
+ *
+ * Author: Cristian Birsan <cristian.birsan@microchip.com>
+ */
+
+#ifndef _LINUX_MFD_SYSCON_AT91_SAMA7_SFR_H
+#define _LINUX_MFD_SYSCON_AT91_SAMA7_SFR_H
+
+#define SAMA7_SFR_OHCIICR 0x00 /* OHCI INT Configuration Register */
+#define SAMA7_SFR_OHCIISR 0x04 /* OHCI INT Status Register */
+/* 0x08 ~ 0xe3: Reserved */
+#define SAMA7_SFR_WPMR 0xe4 /* Write Protection Mode Register */
+#define SAMA7_SFR_WPSR 0xe4 /* Write Protection Status Register */
+/* 0xec ~ 0x200b: Reserved */
+#define SAMA7_SFR_DEBUG 0x200c /* Debug Register */
+
+/* 0x2010 ~ 0x2027: Reserved */
+#define SAMA7_SFR_EHCIOHCI 0x2020 /* EHCI OHCI Clock Configuration Reg */
+
+#define SAMA7_SFR_HSS_AXI_QOS 0x2028 /* HSS AXI QOS Register */
+#define SAMA7_SFR_UDDRC 0x202c /* UDDRC Register */
+#define SAMA7_SFR_CAN_SRAM_SEL 0x2030 /* CAN SRAM Select. Register */
+/* 0x2034 ~ 0x203f: Reserved */
+
+#define SAMA7_SFR_UTMI0 0x2040
+#define SAMA7_SFR_UTMI0R(x) (SAMA7_SFR_UTMI0 + 4 * (x))
+
+#define SAMA7_SFR_UTMI0R0 0x2040 /* UTMI0 Configuration Register */
+#define SAMA7_SFR_UTMI0R1 0x2044 /* UTMI1 Configuration Register */
+#define SAMA7_SFR_UTMI0R2 0x2048 /* UTMI2 Configuration Register */
+
+/* Field definitions */
+#define SAMA7_SFR_OHCIICR_ARIE BIT(0)
+#define SAMA7_SFR_OHCIICR_APPSTART BIT(1)
+#define SAMA7_SFR_OHCIICR_USB_SUSP(x) BIT(8 + (x))
+#define SAMA7_SFR_OHCIICR_USB_SUSPEND GENMASK(10, 8)
+
+#define SAMA7_SFR_OHCIISR_RIS(x) BIT(x)
+
+#define SAMA7_SFR_WPMR_WPEN BIT(0)
+#define SAMA7_SFR_WPMR_KEY 0x53465200 /* SFR in ASCII*/
+#define SAMA7_SFR_WPMR_WPKEY_MASK GENMASK(31, 8)
+
+#define SAMA7_SFR_WPSR_WPSRC_MASK GENMASK(23, 8)
+#define SAMA7_SFR_WPSR_WPVS_MASK BIT(0)
+
+#define SAMA7_SFR_CAN_SRAM_UPPER(x) BIT(x)
+
+#define SAMA7_SFR_UTMI_RX_VBUS BIT(25) /* VBUS Valid bit */
+#define SAMA7_SFR_UTMI_RX_TX_PREEM_AMP_TUNE_1X BIT(23) /* TXPREEMPAMPTUNE 1x */
+#define SAMA7_SFR_UTMI_COMMONON BIT(3) /* PLL Common ON bit */
+
+#define SAMA7_SFR_EHCIOHCI_PHYCLK BIT(1) /* Alternate PHY Clk */
+
+#endif /* _LINUX_MFD_SYSCON_AT91_SAMA7_SFR_H */
diff --git a/arch/arm/mach-at91/include/mach/sama7g5.h b/arch/arm/mach-at91/include/mach/sama7g5.h
index ae43e8700be..621a26f6eba 100644
--- a/arch/arm/mach-at91/include/mach/sama7g5.h
+++ b/arch/arm/mach-at91/include/mach/sama7g5.h
@@ -67,7 +67,35 @@
#define ATMEL_BASE_PIT64BC ATMEL_BASE_PIT64B0
+/* SAMA7G5 series chip id definitions */
+#define ARCH_ID_SAMA7G5 0x80162100
+#define ARCH_EXID_SAMA7G51 0x00000003
+#define ARCH_EXID_SAMA7G52 0x00000002
+#define ARCH_EXID_SAMA7G53 0x00000001
+#define ARCH_EXID_SAMA7G54 0x00000000
+#define ARCH_EXID_SAMA7G54_D1G 0x00000018
+#define ARCH_EXID_SAMA7G54_D2G 0x00000020
+#define ARCH_EXID_SAMA7G54_D4G 0x00000028
+
+#define cpu_is_sama7g5() (get_chip_id() == ARCH_ID_SAMA7G5)
+#define cpu_is_sama7g51() (cpu_is_sama7g5() && \
+ (get_extension_chip_id() == ARCH_EXID_SAMA7G51))
+#define cpu_is_sama7g52() (cpu_is_sama7g5() && \
+ (get_extension_chip_id() == ARCH_EXID_SAMA7G52))
+#define cpu_is_sama7g53() (cpu_is_sama7g5() && \
+ (get_extension_chip_id() == ARCH_EXID_SAMA7G53))
+#define cpu_is_sama7g54() (cpu_is_sama7g5() && \
+ (get_extension_chip_id() == ARCH_EXID_SAMA7G54))
+#define cpu_is_sama7g54d1g() (cpu_is_sama7g5() && \
+ (get_extension_chip_id() == ARCH_EXID_SAMA7G54_D1G))
+#define cpu_is_sama7g54d2g() (cpu_is_sama7g5() && \
+ (get_extension_chip_id() == ARCH_EXID_SAMA7G54_D2G))
+#define cpu_is_sama7g54d4g() (cpu_is_sama7g5() && \
+ (get_extension_chip_id() == ARCH_EXID_SAMA7G54_D4G))
+
#ifndef __ASSEMBLY__
+unsigned int get_chip_id(void);
+unsigned int get_extension_chip_id(void);
char *get_cpu_name(void);
#endif
diff --git a/board/atmel/sam9x60_curiosity/MAINTAINERS b/board/atmel/sam9x60_curiosity/MAINTAINERS
index 3e1dce29803..0d9369e0272 100644
--- a/board/atmel/sam9x60_curiosity/MAINTAINERS
+++ b/board/atmel/sam9x60_curiosity/MAINTAINERS
@@ -5,3 +5,4 @@ S: Maintained
F: board/atmel/sam9x60_curiosity/
F: include/configs/sam9x60_curiosity.h
F: configs/sam9x60_curiosity_mmc_defconfig
+F: configs/sam9x60_curiosity_mmc1_defconfig
diff --git a/board/atmel/sam9x60_curiosity/sam9x60_curiosity.c b/board/atmel/sam9x60_curiosity/sam9x60_curiosity.c
index 8cf67d148dd..0fe0de9fde9 100644
--- a/board/atmel/sam9x60_curiosity/sam9x60_curiosity.c
+++ b/board/atmel/sam9x60_curiosity/sam9x60_curiosity.c
@@ -25,6 +25,13 @@ DECLARE_GLOBAL_DATA_PTR;
void at91_prepare_cpu_var(void);
+static void board_leds_init(void)
+{
+ at91_set_pio_output(AT91_PIO_PORTD, 17, 0); /* LED RED */
+ at91_set_pio_output(AT91_PIO_PORTD, 19, 0); /* LED GREEN */
+ at91_set_pio_output(AT91_PIO_PORTD, 21, 1); /* LED BLUE */
+}
+
int board_late_init(void)
{
at91_prepare_cpu_var();
@@ -62,6 +69,9 @@ int board_init(void)
{
/* address of boot parameters */
gd->bd->bi_boot_params = gd->bd->bi_dram[0].start + 0x100;
+
+ board_leds_init();
+
return 0;
}
diff --git a/board/atmel/sam9x60ek/sam9x60ek.c b/board/atmel/sam9x60ek/sam9x60ek.c
index a3e35f30666..3fbfca4acc9 100644
--- a/board/atmel/sam9x60ek/sam9x60ek.c
+++ b/board/atmel/sam9x60ek/sam9x60ek.c
@@ -24,61 +24,12 @@ DECLARE_GLOBAL_DATA_PTR;
void at91_prepare_cpu_var(void);
-#ifdef CONFIG_CMD_NAND
-static void sam9x60ek_nand_hw_init(void)
+static void board_leds_init(void)
{
- struct at91_smc *smc = (struct at91_smc *)ATMEL_BASE_SMC;
- struct atmel_sfr *sfr = (struct atmel_sfr *)ATMEL_BASE_SFR;
- unsigned int csa;
-
- at91_pio3_set_a_periph(AT91_PIO_PORTD, 0, 1); /* NAND OE */
- at91_pio3_set_a_periph(AT91_PIO_PORTD, 1, 1); /* NAND WE */
- at91_pio3_set_a_periph(AT91_PIO_PORTD, 2, 0); /* NAND ALE */
- at91_pio3_set_a_periph(AT91_PIO_PORTD, 3, 0); /* NAND CLE */
- /* Enable NandFlash */
- at91_set_gpio_output(CFG_SYS_NAND_ENABLE_PIN, 1);
- /* Configure RDY/BSY */
- at91_set_gpio_input(CFG_SYS_NAND_READY_PIN, 1);
- at91_pio3_set_a_periph(AT91_PIO_PORTD, 6, 1);
- at91_pio3_set_a_periph(AT91_PIO_PORTD, 7, 1);
- at91_pio3_set_a_periph(AT91_PIO_PORTD, 8, 1);
- at91_pio3_set_a_periph(AT91_PIO_PORTD, 9, 1);
- at91_pio3_set_a_periph(AT91_PIO_PORTD, 10, 1);
- at91_pio3_set_a_periph(AT91_PIO_PORTD, 11, 1);
- at91_pio3_set_a_periph(AT91_PIO_PORTD, 12, 1);
- at91_pio3_set_a_periph(AT91_PIO_PORTD, 13, 1);
-
- at91_periph_clk_enable(ATMEL_ID_PIOD);
-
- /* Enable CS3 */
- csa = readl(&sfr->ebicsa);
- csa |= AT91_SFR_CCFG_EBI_CSA(3, 1) | AT91_SFR_CCFG_NFD0_ON_D16;
-
- /* Configure IO drive */
- csa &= ~AT91_SFR_CCFG_EBI_DRIVE_SAM9X60;
-
- writel(csa, &sfr->ebicsa);
-
- /* Configure SMC CS3 for NAND/SmartMedia */
- writel(AT91_SMC_SETUP_NWE(4), &smc->cs[3].setup);
-
- writel(AT91_SMC_PULSE_NWE(10) | AT91_SMC_PULSE_NCS_WR(20) |
- AT91_SMC_PULSE_NRD(10) | AT91_SMC_PULSE_NCS_RD(20),
- &smc->cs[3].pulse);
-
- writel(AT91_SMC_CYCLE_NWE(20) | AT91_SMC_CYCLE_NRD(20),
- &smc->cs[3].cycle);
-
- writel(AT91_SMC_MODE_RM_NRD | AT91_SMC_MODE_WM_NWE |
-#ifdef CONFIG_SYS_NAND_DBW_16
- AT91_SMC_MODE_DBW_16 |
-#else /* CONFIG_SYS_NAND_DBW_8 */
- AT91_SMC_MODE_DBW_8 |
-#endif
- AT91_SMC_MODE_TDF | AT91_SMC_MODE_TDF_CYCLE(15),
- &smc->cs[3].mode);
+ at91_set_pio_output(AT91_PIO_PORTB, 11, 0); /* LED RED */
+ at91_set_pio_output(AT91_PIO_PORTB, 12, 0); /* LED GREEN */
+ at91_set_pio_output(AT91_PIO_PORTB, 13, 1); /* LED BLUE */
}
-#endif
#ifdef CONFIG_BOARD_LATE_INIT
int board_late_init(void)
@@ -122,9 +73,8 @@ int board_init(void)
/* address of boot parameters */
gd->bd->bi_boot_params = CFG_SYS_SDRAM_BASE + 0x100;
-#ifdef CONFIG_CMD_NAND
- sam9x60ek_nand_hw_init();
-#endif
+ board_leds_init();
+
return 0;
}
diff --git a/configs/sam9x60_curiosity_mmc1_defconfig b/configs/sam9x60_curiosity_mmc1_defconfig
new file mode 100644
index 00000000000..42aba3d6309
--- /dev/null
+++ b/configs/sam9x60_curiosity_mmc1_defconfig
@@ -0,0 +1,92 @@
+CONFIG_ARM=y
+CONFIG_SKIP_LOWLEVEL_INIT=y
+CONFIG_ARCH_AT91=y
+CONFIG_TEXT_BASE=0x23f00000
+CONFIG_SYS_MALLOC_LEN=0x81000
+CONFIG_SYS_MALLOC_F_LEN=0x8000
+CONFIG_TARGET_SAM9X60_CURIOSITY=y
+CONFIG_ATMEL_LEGACY=y
+CONFIG_NR_DRAM_BANKS=8
+CONFIG_ENV_SIZE=0x4000
+CONFIG_DM_GPIO=y
+CONFIG_DEFAULT_DEVICE_TREE="at91-sam9x60_curiosity"
+CONFIG_SYS_PROMPT="U-Boot> "
+CONFIG_DEBUG_UART_BASE=0xfffff200
+CONFIG_DEBUG_UART_CLOCK=200000000
+CONFIG_DEBUG_UART_BOARD_INIT=y
+CONFIG_SYS_LOAD_ADDR=0x22000000
+CONFIG_DEBUG_UART=y
+CONFIG_HAS_CUSTOM_SYS_INIT_SP_ADDR=y
+CONFIG_CUSTOM_SYS_INIT_SP_ADDR=0x2000bf00
+CONFIG_FIT=y
+CONFIG_SD_BOOT=y
+CONFIG_BOOTDELAY=3
+CONFIG_USE_BOOTARGS=y
+CONFIG_BOOTARGS="console=ttyS0,115200 root=/dev/mmcblk1p2 rw rootwait"
+CONFIG_USE_BOOTCOMMAND=y
+CONFIG_BOOTCOMMAND="fatload mmc 1:1 0x21000000 at91-sam9x60_curiosity.dtb; fatload mmc 1:1 0x22000000 zImage; bootz 0x22000000 - 0x21000000"
+CONFIG_SYS_CONSOLE_IS_IN_ENV=y
+# CONFIG_DISPLAY_BOARDINFO is not set
+CONFIG_MISC_INIT_R=y
+CONFIG_HUSH_PARSER=y
+CONFIG_SYS_CBSIZE=256
+CONFIG_SYS_PBSIZE=281
+CONFIG_CMD_BOOTZ=y
+CONFIG_CMD_CLK=y
+CONFIG_CMD_DM=y
+CONFIG_CMD_GPIO=y
+CONFIG_CMD_I2C=y
+CONFIG_CMD_MMC=y
+CONFIG_CMD_NAND=y
+CONFIG_CMD_NAND_TRIMFFS=y
+# CONFIG_CMD_SETEXPR is not set
+CONFIG_CMD_DHCP=y
+CONFIG_BOOTP_BOOTFILESIZE=y
+CONFIG_CMD_MII=y
+CONFIG_CMD_PING=y
+CONFIG_CMD_HASH=y
+CONFIG_HASH_VERIFY=y
+CONFIG_CMD_FAT=y
+CONFIG_OF_CONTROL=y
+CONFIG_ENV_IS_IN_FAT=y
+CONFIG_ENV_FAT_DEVICE_AND_PART="1:1"
+CONFIG_SYS_RELOC_GD_ENV_ADDR=y
+CONFIG_DM=y
+CONFIG_REGMAP=y
+CONFIG_SYSCON=y
+CONFIG_CLK=y
+CONFIG_CLK_CCF=y
+CONFIG_CLK_AT91=y
+CONFIG_AT91_GENERIC_CLK=y
+CONFIG_AT91_SAM9X60_PLL=y
+CONFIG_CPU=y
+CONFIG_AT91_GPIO=y
+CONFIG_DM_I2C=y
+CONFIG_SYS_I2C_AT91=y
+CONFIG_ATMEL_EBI=y
+CONFIG_MFD_ATMEL_SMC=y
+CONFIG_I2C_EEPROM=y
+CONFIG_MICROCHIP_FLEXCOM=y
+CONFIG_MMC_SDHCI=y
+CONFIG_MMC_SDHCI_ATMEL=y
+CONFIG_MTD=y
+CONFIG_DM_MTD=y
+CONFIG_MTD_RAW_NAND=y
+CONFIG_DM_NAND_ATMEL=y
+CONFIG_SYS_NAND_ONFI_DETECTION=y
+CONFIG_PHY_MICREL=y
+CONFIG_MACB=y
+CONFIG_PINCTRL=y
+CONFIG_PINCTRL_AT91=y
+CONFIG_DM_SERIAL=y
+CONFIG_DEBUG_UART_ANNOUNCE=y
+CONFIG_ATMEL_USART=y
+CONFIG_SYSRESET=y
+CONFIG_SYSRESET_AT91=y
+CONFIG_TIMER=y
+CONFIG_MCHP_PIT64B_TIMER=y
+CONFIG_W1=y
+CONFIG_W1_GPIO=y
+CONFIG_W1_EEPROM=y
+CONFIG_W1_EEPROM_DS24XXX=y
+CONFIG_OF_LIBFDT_OVERLAY=y
diff --git a/configs/sam9x60ek_mmc_defconfig b/configs/sam9x60ek_mmc_defconfig
index f83cbf6f15b..268a485456c 100644
--- a/configs/sam9x60ek_mmc_defconfig
+++ b/configs/sam9x60ek_mmc_defconfig
@@ -52,6 +52,8 @@ CONFIG_OF_CONTROL=y
CONFIG_ENV_IS_IN_FAT=y
CONFIG_ENV_FAT_DEVICE_AND_PART="0:1"
CONFIG_SYS_RELOC_GD_ENV_ADDR=y
+CONFIG_REGMAP=y
+CONFIG_SYSCON=y
CONFIG_CLK=y
CONFIG_CLK_CCF=y
CONFIG_CLK_AT91=y
@@ -61,15 +63,16 @@ CONFIG_CPU=y
CONFIG_AT91_GPIO=y
CONFIG_DM_I2C=y
CONFIG_SYS_I2C_AT91=y
+CONFIG_ATMEL_EBI=y
+CONFIG_MFD_ATMEL_SMC=y
CONFIG_I2C_EEPROM=y
CONFIG_MICROCHIP_FLEXCOM=y
CONFIG_MMC_SDHCI=y
CONFIG_MMC_SDHCI_ATMEL=y
CONFIG_MTD=y
+CONFIG_DM_MTD=y
CONFIG_MTD_RAW_NAND=y
-CONFIG_NAND_ATMEL=y
-CONFIG_ATMEL_NAND_HW_PMECC=y
-CONFIG_PMECC_CAP=8
+CONFIG_DM_NAND_ATMEL=y
CONFIG_SYS_NAND_ONFI_DETECTION=y
CONFIG_DM_SPI_FLASH=y
CONFIG_SF_DEFAULT_SPEED=50000000
diff --git a/configs/sam9x60ek_nandflash_defconfig b/configs/sam9x60ek_nandflash_defconfig
index b04a153640d..a9cbb6e953a 100644
--- a/configs/sam9x60ek_nandflash_defconfig
+++ b/configs/sam9x60ek_nandflash_defconfig
@@ -54,6 +54,8 @@ CONFIG_OF_CONTROL=y
CONFIG_ENV_IS_IN_NAND=y
CONFIG_SYS_REDUNDAND_ENVIRONMENT=y
CONFIG_SYS_RELOC_GD_ENV_ADDR=y
+CONFIG_REGMAP=y
+CONFIG_SYSCON=y
CONFIG_CLK=y
CONFIG_CLK_CCF=y
CONFIG_CLK_AT91=y
@@ -63,14 +65,15 @@ CONFIG_CPU=y
CONFIG_AT91_GPIO=y
CONFIG_DM_I2C=y
CONFIG_SYS_I2C_AT91=y
+CONFIG_ATMEL_EBI=y
+CONFIG_MFD_ATMEL_SMC=y
CONFIG_I2C_EEPROM=y
CONFIG_MICROCHIP_FLEXCOM=y
CONFIG_GENERIC_ATMEL_MCI=y
CONFIG_MTD=y
+CONFIG_DM_MTD=y
# CONFIG_SYS_NAND_USE_FLASH_BBT is not set
-CONFIG_NAND_ATMEL=y
-CONFIG_ATMEL_NAND_HW_PMECC=y
-CONFIG_PMECC_CAP=8
+CONFIG_DM_NAND_ATMEL=y
CONFIG_SYS_NAND_ONFI_DETECTION=y
CONFIG_DM_SPI_FLASH=y
CONFIG_SF_DEFAULT_SPEED=50000000
diff --git a/configs/sam9x60ek_qspiflash_defconfig b/configs/sam9x60ek_qspiflash_defconfig
index fd95a6fbbba..72f08f1375d 100644
--- a/configs/sam9x60ek_qspiflash_defconfig
+++ b/configs/sam9x60ek_qspiflash_defconfig
@@ -54,6 +54,8 @@ CONFIG_OF_CONTROL=y
CONFIG_ENV_IS_IN_SPI_FLASH=y
CONFIG_ENV_SPI_MAX_HZ=50000000
CONFIG_SYS_RELOC_GD_ENV_ADDR=y
+CONFIG_REGMAP=y
+CONFIG_SYSCON=y
CONFIG_CLK=y
CONFIG_CLK_CCF=y
CONFIG_CLK_AT91=y
@@ -63,6 +65,8 @@ CONFIG_CPU=y
CONFIG_AT91_GPIO=y
CONFIG_DM_I2C=y
CONFIG_SYS_I2C_AT91=y
+CONFIG_ATMEL_EBI=y
+CONFIG_MFD_ATMEL_SMC=y
CONFIG_I2C_EEPROM=y
CONFIG_MICROCHIP_FLEXCOM=y
CONFIG_GENERIC_ATMEL_MCI=y
@@ -70,9 +74,7 @@ CONFIG_MTD=y
CONFIG_DM_MTD=y
CONFIG_MTD_RAW_NAND=y
# CONFIG_SYS_NAND_USE_FLASH_BBT is not set
-CONFIG_NAND_ATMEL=y
-CONFIG_ATMEL_NAND_HW_PMECC=y
-CONFIG_PMECC_CAP=8
+CONFIG_DM_NAND_ATMEL=y
CONFIG_SYS_NAND_ONFI_DETECTION=y
CONFIG_DM_SPI_FLASH=y
CONFIG_SPI_FLASH_MACRONIX=y
diff --git a/drivers/Kconfig b/drivers/Kconfig
index 75ac149d311..9101e538b09 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -62,6 +62,8 @@ source "drivers/mailbox/Kconfig"
source "drivers/memory/Kconfig"
+source "drivers/mfd/Kconfig"
+
source "drivers/misc/Kconfig"
source "drivers/mmc/Kconfig"
diff --git a/drivers/Makefile b/drivers/Makefile
index 6f1de58e003..83b14ef1fd3 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -103,6 +103,7 @@ obj-$(CONFIG_QE) += qe/
obj-$(CONFIG_U_QE) += qe/
obj-y += mailbox/
obj-y += memory/
+obj-y += mfd/
obj-y += mtd/
obj-y += pwm/
obj-y += reset/
diff --git a/drivers/memory/Kconfig b/drivers/memory/Kconfig
index 56b89f17be5..22cb9d637c5 100644
--- a/drivers/memory/Kconfig
+++ b/drivers/memory/Kconfig
@@ -13,6 +13,13 @@ config MEMORY
SRAM, Ethernet adapters, FPGAs, etc.
For now this uclass has no methods yet.
+config ATMEL_EBI
+ bool "Support for Atmel EBI"
+ help
+ Driver for Atmel EBI controller. This is a dummy
+ driver. Doesn't provide an access to EBI controller. Select
+ this option to enable the NAND flash controller driver
+
config SANDBOX_MEMORY
bool "Enable Sandbox Memory Controller driver"
depends on SANDBOX && MEMORY
diff --git a/drivers/memory/Makefile b/drivers/memory/Makefile
index 2b196d78c0b..1cabf8ac9cd 100644
--- a/drivers/memory/Makefile
+++ b/drivers/memory/Makefile
@@ -2,5 +2,6 @@
obj-$(CONFIG_MEMORY) += memory-uclass.o
obj-$(CONFIG_SANDBOX_MEMORY) += memory-sandbox.o
obj-$(CONFIG_STM32_FMC2_EBI) += stm32-fmc2-ebi.o
+obj-$(CONFIG_ATMEL_EBI) += atmel_ebi.o
obj-$(CONFIG_TI_AEMIF) += ti-aemif.o
obj-$(CONFIG_TI_GPMC) += ti-gpmc.o
diff --git a/drivers/memory/atmel_ebi.c b/drivers/memory/atmel_ebi.c
new file mode 100644
index 00000000000..4739eef1b75
--- /dev/null
+++ b/drivers/memory/atmel_ebi.c
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2022 Microchip Technology Inc. and its subsidiaries
+ */
+
+#include <dm/device.h>
+#include <dm/read.h>
+#include <dm/uclass.h>
+#include <fdtdec.h>
+
+static int atmel_ebi_probe(struct udevice *dev)
+{
+ int ret;
+ struct udevice *ndev;
+
+ ret = uclass_get_device_by_driver(UCLASS_MTD,
+ DM_DRIVER_GET(atmel_nand_controller),
+ &ndev);
+ if (ret)
+ printf("Failed to probe nand driver (err = %d)\n", ret);
+
+ return ret;
+}
+
+static const struct udevice_id atmel_ebi_match[] = {
+ {.compatible = "microchip,sam9x60-ebi"},
+ {.compatible = "atmel,sama5d3-ebi"},
+ { /* Sentinel */ }
+};
+
+U_BOOT_DRIVER(atmel_ebi) = {
+ .name = "atmel_ebi",
+ .id = UCLASS_NOP,
+ .of_match = atmel_ebi_match,
+ .probe = atmel_ebi_probe,
+ .bind = dm_scan_fdt_dev,
+};
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
new file mode 100644
index 00000000000..ae53b02f27c
--- /dev/null
+++ b/drivers/mfd/Kconfig
@@ -0,0 +1,4 @@
+config MFD_ATMEL_SMC
+ bool "Atmel Static Memory Controller driver"
+ help
+ Say yes here to support Atmel Static Memory Controller driver.
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
new file mode 100644
index 00000000000..4454815a981
--- /dev/null
+++ b/drivers/mfd/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_MFD_ATMEL_SMC) += atmel-smc.o
diff --git a/drivers/mfd/atmel-smc.c b/drivers/mfd/atmel-smc.c
new file mode 100644
index 00000000000..15296f71a16
--- /dev/null
+++ b/drivers/mfd/atmel-smc.c
@@ -0,0 +1,364 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Atmel SMC (Static Memory Controller) helper functions.
+ *
+ * Copyright (C) 2022 Microchip Technology Inc.
+ * Copyright (C) 2017 Free Electrons
+ *
+ * Author: Boris Brezillon <boris.brezillon@free-electrons.com>
+ */
+
+#include <clk.h>
+#include <dm/device.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/mfd/syscon/atmel-smc.h>
+#include <linux/string.h>
+
+/**
+ * atmel_smc_cs_conf_init - initialize a SMC CS conf
+ * @conf: the SMC CS conf to initialize
+ *
+ * Set all fields to 0 so that one can start defining a new config.
+ */
+void atmel_smc_cs_conf_init(struct atmel_smc_cs_conf *conf)
+{
+ memset(conf, 0, sizeof(*conf));
+}
+EXPORT_SYMBOL_GPL(atmel_smc_cs_conf_init);
+
+/**
+ * atmel_smc_cs_encode_ncycles - encode a number of MCK clk cycles in the
+ * format expected by the SMC engine
+ * @ncycles: number of MCK clk cycles
+ * @msbpos: position of the MSB part of the timing field
+ * @msbwidth: width of the MSB part of the timing field
+ * @msbfactor: factor applied to the MSB
+ * @encodedval: param used to store the encoding result
+ *
+ * This function encodes the @ncycles value as described in the datasheet
+ * (section "SMC Setup/Pulse/Cycle/Timings Register"). This is a generic
+ * helper which called with different parameter depending on the encoding
+ * scheme.
+ *
+ * If the @ncycles value is too big to be encoded, -ERANGE is returned and
+ * the encodedval is contains the maximum val. Otherwise, 0 is returned.
+ */
+static int atmel_smc_cs_encode_ncycles(unsigned int ncycles,
+ unsigned int msbpos,
+ unsigned int msbwidth,
+ unsigned int msbfactor,
+ unsigned int *encodedval)
+{
+ unsigned int lsbmask = GENMASK(msbpos - 1, 0);
+ unsigned int msbmask = GENMASK(msbwidth - 1, 0);
+ unsigned int msb, lsb;
+ int ret = 0;
+
+ msb = ncycles / msbfactor;
+ lsb = ncycles % msbfactor;
+
+ if (lsb > lsbmask) {
+ lsb = 0;
+ msb++;
+ }
+
+ /*
+ * Let's just put the maximum we can if the requested setting does
+ * not fit in the register field.
+ * We still return -ERANGE in case the caller cares.
+ */
+ if (msb > msbmask) {
+ msb = msbmask;
+ lsb = lsbmask;
+ ret = -ERANGE;
+ }
+
+ *encodedval = (msb << msbpos) | lsb;
+
+ return ret;
+}
+
+/**
+ * atmel_smc_cs_conf_set_timing - set the SMC CS conf Txx parameter to a
+ * specific value
+ * @conf: SMC CS conf descriptor
+ * @shift: the position of the Txx field in the TIMINGS register
+ * @ncycles: value (expressed in MCK clk cycles) to assign to this Txx
+ * parameter
+ *
+ * This function encodes the @ncycles value as described in the datasheet
+ * (section "SMC Timings Register"), and then stores the result in the
+ * @conf->timings field at @shift position.
+ *
+ * Returns -EINVAL if shift is invalid, -ERANGE if ncycles does not fit in
+ * the field, and 0 otherwise.
+ */
+int atmel_smc_cs_conf_set_timing(struct atmel_smc_cs_conf *conf,
+ unsigned int shift, unsigned int ncycles)
+{
+ unsigned int val;
+ int ret;
+
+ if (shift != ATMEL_HSMC_TIMINGS_TCLR_SHIFT &&
+ shift != ATMEL_HSMC_TIMINGS_TADL_SHIFT &&
+ shift != ATMEL_HSMC_TIMINGS_TAR_SHIFT &&
+ shift != ATMEL_HSMC_TIMINGS_TRR_SHIFT &&
+ shift != ATMEL_HSMC_TIMINGS_TWB_SHIFT)
+ return -EINVAL;
+
+ /*
+ * The formula described in atmel datasheets (section "HSMC Timings
+ * Register"):
+ *
+ * ncycles = (Txx[3] * 64) + Txx[2:0]
+ */
+ ret = atmel_smc_cs_encode_ncycles(ncycles, 3, 1, 64, &val);
+ conf->timings &= ~GENMASK(shift + 3, shift);
+ conf->timings |= val << shift;
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(atmel_smc_cs_conf_set_timing);
+
+/**
+ * atmel_smc_cs_conf_set_setup - set the SMC CS conf xx_SETUP parameter to a
+ * specific value
+ * @conf: SMC CS conf descriptor
+ * @shift: the position of the xx_SETUP field in the SETUP register
+ * @ncycles: value (expressed in MCK clk cycles) to assign to this xx_SETUP
+ * parameter
+ *
+ * This function encodes the @ncycles value as described in the datasheet
+ * (section "SMC Setup Register"), and then stores the result in the
+ * @conf->setup field at @shift position.
+ *
+ * Returns -EINVAL if @shift is invalid, -ERANGE if @ncycles does not fit in
+ * the field, and 0 otherwise.
+ */
+int atmel_smc_cs_conf_set_setup(struct atmel_smc_cs_conf *conf,
+ unsigned int shift, unsigned int ncycles)
+{
+ unsigned int val;
+ int ret;
+
+ if (shift != ATMEL_SMC_NWE_SHIFT && shift != ATMEL_SMC_NCS_WR_SHIFT &&
+ shift != ATMEL_SMC_NRD_SHIFT && shift != ATMEL_SMC_NCS_RD_SHIFT)
+ return -EINVAL;
+
+ /*
+ * The formula described in atmel datasheets (section "SMC Setup
+ * Register"):
+ *
+ * ncycles = (128 * xx_SETUP[5]) + xx_SETUP[4:0]
+ */
+ ret = atmel_smc_cs_encode_ncycles(ncycles, 5, 1, 128, &val);
+ conf->setup &= ~GENMASK(shift + 7, shift);
+ conf->setup |= val << shift;
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(atmel_smc_cs_conf_set_setup);
+
+/**
+ * atmel_smc_cs_conf_set_pulse - set the SMC CS conf xx_PULSE parameter to a
+ * specific value
+ * @conf: SMC CS conf descriptor
+ * @shift: the position of the xx_PULSE field in the PULSE register
+ * @ncycles: value (expressed in MCK clk cycles) to assign to this xx_PULSE
+ * parameter
+ *
+ * This function encodes the @ncycles value as described in the datasheet
+ * (section "SMC Pulse Register"), and then stores the result in the
+ * @conf->setup field at @shift position.
+ *
+ * Returns -EINVAL if @shift is invalid, -ERANGE if @ncycles does not fit in
+ * the field, and 0 otherwise.
+ */
+int atmel_smc_cs_conf_set_pulse(struct atmel_smc_cs_conf *conf,
+ unsigned int shift, unsigned int ncycles)
+{
+ unsigned int val;
+ int ret;
+
+ if (shift != ATMEL_SMC_NWE_SHIFT && shift != ATMEL_SMC_NCS_WR_SHIFT &&
+ shift != ATMEL_SMC_NRD_SHIFT && shift != ATMEL_SMC_NCS_RD_SHIFT)
+ return -EINVAL;
+
+ /*
+ * The formula described in atmel datasheets (section "SMC Pulse
+ * Register"):
+ *
+ * ncycles = (256 * xx_PULSE[6]) + xx_PULSE[5:0]
+ */
+ ret = atmel_smc_cs_encode_ncycles(ncycles, 6, 1, 256, &val);
+ conf->pulse &= ~GENMASK(shift + 7, shift);
+ conf->pulse |= val << shift;
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(atmel_smc_cs_conf_set_pulse);
+
+/**
+ * atmel_smc_cs_conf_set_cycle - set the SMC CS conf xx_CYCLE parameter to a
+ * specific value
+ * @conf: SMC CS conf descriptor
+ * @shift: the position of the xx_CYCLE field in the CYCLE register
+ * @ncycles: value (expressed in MCK clk cycles) to assign to this xx_CYCLE
+ * parameter
+ *
+ * This function encodes the @ncycles value as described in the datasheet
+ * (section "SMC Cycle Register"), and then stores the result in the
+ * @conf->setup field at @shift position.
+ *
+ * Returns -EINVAL if @shift is invalid, -ERANGE if @ncycles does not fit in
+ * the field, and 0 otherwise.
+ */
+int atmel_smc_cs_conf_set_cycle(struct atmel_smc_cs_conf *conf,
+ unsigned int shift, unsigned int ncycles)
+{
+ unsigned int val;
+ int ret;
+
+ if (shift != ATMEL_SMC_NWE_SHIFT && shift != ATMEL_SMC_NRD_SHIFT)
+ return -EINVAL;
+
+ /*
+ * The formula described in atmel datasheets (section "SMC Cycle
+ * Register"):
+ *
+ * ncycles = (xx_CYCLE[8:7] * 256) + xx_CYCLE[6:0]
+ */
+ ret = atmel_smc_cs_encode_ncycles(ncycles, 7, 2, 256, &val);
+ conf->cycle &= ~GENMASK(shift + 15, shift);
+ conf->cycle |= val << shift;
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(atmel_smc_cs_conf_set_cycle);
+
+/**
+ * atmel_smc_cs_conf_apply - apply an SMC CS conf
+ * @regmap: the SMC regmap
+ * @cs: the CS id
+ * @conf: the SMC CS conf to apply
+ *
+ * Applies an SMC CS configuration.
+ * Only valid on at91sam9/avr32 SoCs.
+ */
+void atmel_smc_cs_conf_apply(struct regmap *regmap, int cs,
+ const struct atmel_smc_cs_conf *conf)
+{
+ regmap_write(regmap, ATMEL_SMC_SETUP(cs), conf->setup);
+ regmap_write(regmap, ATMEL_SMC_PULSE(cs), conf->pulse);
+ regmap_write(regmap, ATMEL_SMC_CYCLE(cs), conf->cycle);
+ regmap_write(regmap, ATMEL_SMC_MODE(cs), conf->mode);
+}
+EXPORT_SYMBOL_GPL(atmel_smc_cs_conf_apply);
+
+/**
+ * atmel_hsmc_cs_conf_apply - apply an SMC CS conf
+ * @regmap: the HSMC regmap
+ * @cs: the CS id
+ * @layout: the layout of registers
+ * @conf: the SMC CS conf to apply
+ *
+ * Applies an SMC CS configuration.
+ * Only valid on post-sama5 SoCs.
+ */
+void atmel_hsmc_cs_conf_apply(struct regmap *regmap,
+ const struct atmel_hsmc_reg_layout *layout,
+ int cs, const struct atmel_smc_cs_conf *conf)
+{
+ regmap_write(regmap, ATMEL_HSMC_SETUP(layout, cs), conf->setup);
+ regmap_write(regmap, ATMEL_HSMC_PULSE(layout, cs), conf->pulse);
+ regmap_write(regmap, ATMEL_HSMC_CYCLE(layout, cs), conf->cycle);
+ regmap_write(regmap, ATMEL_HSMC_TIMINGS(layout, cs), conf->timings);
+ regmap_write(regmap, ATMEL_HSMC_MODE(layout, cs), conf->mode);
+}
+EXPORT_SYMBOL_GPL(atmel_hsmc_cs_conf_apply);
+
+/**
+ * atmel_smc_cs_conf_get - retrieve the current SMC CS conf
+ * @regmap: the SMC regmap
+ * @cs: the CS id
+ * @conf: the SMC CS conf object to store the current conf
+ *
+ * Retrieve the SMC CS configuration.
+ * Only valid on at91sam9/avr32 SoCs.
+ */
+void atmel_smc_cs_conf_get(struct regmap *regmap, int cs,
+ struct atmel_smc_cs_conf *conf)
+{
+ regmap_read(regmap, ATMEL_SMC_SETUP(cs), &conf->setup);
+ regmap_read(regmap, ATMEL_SMC_PULSE(cs), &conf->pulse);
+ regmap_read(regmap, ATMEL_SMC_CYCLE(cs), &conf->cycle);
+ regmap_read(regmap, ATMEL_SMC_MODE(cs), &conf->mode);
+}
+EXPORT_SYMBOL_GPL(atmel_smc_cs_conf_get);
+
+/**
+ * atmel_hsmc_cs_conf_get - retrieve the current SMC CS conf
+ * @regmap: the HSMC regmap
+ * @cs: the CS id
+ * @layout: the layout of registers
+ * @conf: the SMC CS conf object to store the current conf
+ *
+ * Retrieve the SMC CS configuration.
+ * Only valid on post-sama5 SoCs.
+ */
+void atmel_hsmc_cs_conf_get(struct regmap *regmap,
+ const struct atmel_hsmc_reg_layout *layout,
+ int cs, struct atmel_smc_cs_conf *conf)
+{
+ regmap_read(regmap, ATMEL_HSMC_SETUP(layout, cs), &conf->setup);
+ regmap_read(regmap, ATMEL_HSMC_PULSE(layout, cs), &conf->pulse);
+ regmap_read(regmap, ATMEL_HSMC_CYCLE(layout, cs), &conf->cycle);
+ regmap_read(regmap, ATMEL_HSMC_TIMINGS(layout, cs), &conf->timings);
+ regmap_read(regmap, ATMEL_HSMC_MODE(layout, cs), &conf->mode);
+}
+EXPORT_SYMBOL_GPL(atmel_hsmc_cs_conf_get);
+
+static const struct atmel_hsmc_reg_layout sama5d3_reg_layout = {
+ .timing_regs_offset = 0x600,
+};
+
+static const struct atmel_hsmc_reg_layout sama5d2_reg_layout = {
+ .timing_regs_offset = 0x700,
+};
+
+static const struct udevice_id atmel_smc_ids[] = {
+ { .compatible = "atmel,at91sam9260-smc", .data = (ulong)0 },
+ { .compatible = "atmel,sama5d3-smc", .data = (ulong)&sama5d3_reg_layout },
+ { .compatible = "atmel,sama5d2-smc", .data = (ulong)&sama5d2_reg_layout },
+ { /* sentinel */ },
+};
+
+/**
+ * atmel_hsmc_get_reg_layout - retrieve the layout of HSMC registers
+ * @np: the HSMC regmap
+ *
+ * Retrieve the layout of HSMC registers.
+ *
+ * Returns NULL in case of SMC, a struct atmel_hsmc_reg_layout pointer
+ * in HSMC case, otherwise ERR_PTR(-EINVAL).
+ */
+const struct atmel_hsmc_reg_layout *
+atmel_hsmc_get_reg_layout(ofnode np)
+{
+ int i;
+ const struct udevice_id *match;
+ const char *name;
+ int len;
+
+ name = ofnode_get_property(np, "compatible", &len);
+
+ for (i = 0; i < ARRAY_SIZE(atmel_smc_ids); i++) {
+ if (!strcmp(name, atmel_smc_ids[i].compatible)) {
+ match = &atmel_smc_ids[i];
+ break;
+ }
+ }
+
+ return match ? (struct atmel_hsmc_reg_layout *)match->data : ERR_PTR(-EINVAL);
+}
diff --git a/drivers/mtd/nand/raw/Kconfig b/drivers/mtd/nand/raw/Kconfig
index f8b79e54561..ab719a2ff18 100644
--- a/drivers/mtd/nand/raw/Kconfig
+++ b/drivers/mtd/nand/raw/Kconfig
@@ -45,6 +45,14 @@ config SYS_NAND_NO_SUBPAGE_WRITE
bool "Disable subpage write support"
depends on NAND_ARASAN || NAND_DAVINCI || NAND_KIRKWOOD
+config DM_NAND_ATMEL
+ bool "Support Atmel NAND controller with DM support"
+ select SYS_NAND_SELF_INIT
+ imply SYS_NAND_USE_FLASH_BBT
+ help
+ Enable this driver for NAND flash platforms using an Atmel NAND
+ controller.
+
config NAND_ATMEL
bool "Support Atmel NAND controller"
select SYS_NAND_SELF_INIT
diff --git a/drivers/mtd/nand/raw/Makefile b/drivers/mtd/nand/raw/Makefile
index a398aa9d886..42c1fb25b41 100644
--- a/drivers/mtd/nand/raw/Makefile
+++ b/drivers/mtd/nand/raw/Makefile
@@ -48,6 +48,7 @@ ifdef NORMAL_DRIVERS
obj-$(CONFIG_NAND_ECC_BCH) += nand_bch.o
obj-$(CONFIG_NAND_ATMEL) += atmel_nand.o
+obj-$(CONFIG_DM_NAND_ATMEL) += atmel/
obj-$(CONFIG_NAND_ARASAN) += arasan_nfc.o
obj-$(CONFIG_NAND_BRCMNAND) += brcmnand/
obj-$(CONFIG_NAND_DAVINCI) += davinci_nand.o
diff --git a/drivers/mtd/nand/raw/atmel/Makefile b/drivers/mtd/nand/raw/atmel/Makefile
new file mode 100644
index 00000000000..e044ff55ba4
--- /dev/null
+++ b/drivers/mtd/nand/raw/atmel/Makefile
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_DM_NAND_ATMEL) += atmel-nand-controller.o atmel-pmecc.o
+
+atmel-nand-controller-objs := nand-controller.o
+atmel-pmecc-objs := pmecc.o
diff --git a/drivers/mtd/nand/raw/atmel/nand-controller.c b/drivers/mtd/nand/raw/atmel/nand-controller.c
new file mode 100644
index 00000000000..9873d112544
--- /dev/null
+++ b/drivers/mtd/nand/raw/atmel/nand-controller.c
@@ -0,0 +1,2286 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2022 ATMEL
+ * Copyright 2017 Free Electrons
+ *
+ * Author: Boris Brezillon <boris.brezillon@free-electrons.com>
+ *
+ * Derived from the atmel_nand.c driver which contained the following
+ * copyrights:
+ *
+ * Copyright 2003 Rick Bronson
+ *
+ * Derived from drivers/mtd/nand/autcpu12.c (removed in v3.8)
+ * Copyright 2001 Thomas Gleixner (gleixner@autronix.de)
+ *
+ * Derived from drivers/mtd/spia.c (removed in v3.8)
+ * Copyright 2000 Steven J. Hill (sjhill@cotw.com)
+ *
+ *
+ * Add Hardware ECC support for AT91SAM9260 / AT91SAM9263
+ * Richard Genoud (richard.genoud@gmail.com), Adeneo Copyright 2007
+ *
+ * Derived from Das U-Boot source code
+ * (u-boot-1.1.5/board/atmel/at91sam9263ek/nand.c)
+ * Copyright 2006 ATMEL Rousset, Lacressonniere Nicolas
+ *
+ * Add Programmable Multibit ECC support for various AT91 SoC
+ * Copyright 2012 ATMEL, Hong Xu
+ *
+ * Add Nand Flash Controller support for SAMA5 SoC
+ * Copyright 2013 ATMEL, Josh Wu (josh.wu@atmel.com)
+ *
+ * Port from Linux
+ * Balamanikandan Gunasundar(balamanikandan.gunasundar@microchip.com)
+ * Copyright (C) 2022 Microchip Technology Inc.
+ *
+ * A few words about the naming convention in this file. This convention
+ * applies to structure and function names.
+ *
+ * Prefixes:
+ *
+ * - atmel_nand_: all generic structures/functions
+ * - atmel_smc_nand_: all structures/functions specific to the SMC interface
+ * (at91sam9 and avr32 SoCs)
+ * - atmel_hsmc_nand_: all structures/functions specific to the HSMC interface
+ * (sama5 SoCs and later)
+ * - atmel_nfc_: all structures/functions used to manipulate the NFC sub-block
+ * that is available in the HSMC block
+ * - <soc>_nand_: all SoC specific structures/functions
+ */
+
+#include <asm-generic/gpio.h>
+#include <clk.h>
+#include <dm/device_compat.h>
+#include <dm/devres.h>
+#include <dm/of_addr.h>
+#include <dm/of_access.h>
+#include <dm/uclass.h>
+#include <linux/completion.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/ioport.h>
+#include <linux/mfd/syscon/atmel-matrix.h>
+#include <linux/mfd/syscon/atmel-smc.h>
+#include <linux/mtd/rawnand.h>
+#include <linux/mtd/mtd.h>
+#include <mach/at91_sfr.h>
+#include <nand.h>
+#include <regmap.h>
+#include <syscon.h>
+
+#include "pmecc.h"
+
+#define NSEC_PER_SEC 1000000000L
+
+#define ATMEL_HSMC_NFC_CFG 0x0
+#define ATMEL_HSMC_NFC_CFG_SPARESIZE(x) (((x) / 4) << 24)
+#define ATMEL_HSMC_NFC_CFG_SPARESIZE_MASK GENMASK(30, 24)
+#define ATMEL_HSMC_NFC_CFG_DTO(cyc, mul) (((cyc) << 16) | ((mul) << 20))
+#define ATMEL_HSMC_NFC_CFG_DTO_MAX GENMASK(22, 16)
+#define ATMEL_HSMC_NFC_CFG_RBEDGE BIT(13)
+#define ATMEL_HSMC_NFC_CFG_FALLING_EDGE BIT(12)
+#define ATMEL_HSMC_NFC_CFG_RSPARE BIT(9)
+#define ATMEL_HSMC_NFC_CFG_WSPARE BIT(8)
+#define ATMEL_HSMC_NFC_CFG_PAGESIZE_MASK GENMASK(2, 0)
+#define ATMEL_HSMC_NFC_CFG_PAGESIZE(x) (fls((x) / 512) - 1)
+
+#define ATMEL_HSMC_NFC_CTRL 0x4
+#define ATMEL_HSMC_NFC_CTRL_EN BIT(0)
+#define ATMEL_HSMC_NFC_CTRL_DIS BIT(1)
+
+#define ATMEL_HSMC_NFC_SR 0x8
+#define ATMEL_HSMC_NFC_IER 0xc
+#define ATMEL_HSMC_NFC_IDR 0x10
+#define ATMEL_HSMC_NFC_IMR 0x14
+#define ATMEL_HSMC_NFC_SR_ENABLED BIT(1)
+#define ATMEL_HSMC_NFC_SR_RB_RISE BIT(4)
+#define ATMEL_HSMC_NFC_SR_RB_FALL BIT(5)
+#define ATMEL_HSMC_NFC_SR_BUSY BIT(8)
+#define ATMEL_HSMC_NFC_SR_WR BIT(11)
+#define ATMEL_HSMC_NFC_SR_CSID GENMASK(14, 12)
+#define ATMEL_HSMC_NFC_SR_XFRDONE BIT(16)
+#define ATMEL_HSMC_NFC_SR_CMDDONE BIT(17)
+#define ATMEL_HSMC_NFC_SR_DTOE BIT(20)
+#define ATMEL_HSMC_NFC_SR_UNDEF BIT(21)
+#define ATMEL_HSMC_NFC_SR_AWB BIT(22)
+#define ATMEL_HSMC_NFC_SR_NFCASE BIT(23)
+#define ATMEL_HSMC_NFC_SR_ERRORS (ATMEL_HSMC_NFC_SR_DTOE | \
+ ATMEL_HSMC_NFC_SR_UNDEF | \
+ ATMEL_HSMC_NFC_SR_AWB | \
+ ATMEL_HSMC_NFC_SR_NFCASE)
+#define ATMEL_HSMC_NFC_SR_RBEDGE(x) BIT((x) + 24)
+
+#define ATMEL_HSMC_NFC_ADDR 0x18
+#define ATMEL_HSMC_NFC_BANK 0x1c
+
+#define ATMEL_NFC_MAX_RB_ID 7
+
+#define ATMEL_NFC_SRAM_SIZE 0x2400
+
+#define ATMEL_NFC_CMD(pos, cmd) ((cmd) << (((pos) * 8) + 2))
+#define ATMEL_NFC_VCMD2 BIT(18)
+#define ATMEL_NFC_ACYCLE(naddrs) ((naddrs) << 19)
+#define ATMEL_NFC_CSID(cs) ((cs) << 22)
+#define ATMEL_NFC_DATAEN BIT(25)
+#define ATMEL_NFC_NFCWR BIT(26)
+
+#define ATMEL_NFC_MAX_ADDR_CYCLES 5
+
+#define ATMEL_NAND_ALE_OFFSET BIT(21)
+#define ATMEL_NAND_CLE_OFFSET BIT(22)
+
+#define DEFAULT_TIMEOUT_MS 1000
+#define MIN_DMA_LEN 128
+
+static struct nand_ecclayout atmel_pmecc_oobinfo;
+
+struct nand_controller_ops {
+ int (*attach_chip)(struct nand_chip *chip);
+ int (*setup_data_interface)(struct mtd_info *mtd, int chipnr,
+ const struct nand_data_interface *conf);
+};
+
+struct nand_controller {
+ const struct nand_controller_ops *ops;
+};
+
+enum atmel_nand_rb_type {
+ ATMEL_NAND_NO_RB,
+ ATMEL_NAND_NATIVE_RB,
+ ATMEL_NAND_GPIO_RB,
+};
+
+struct atmel_nand_rb {
+ enum atmel_nand_rb_type type;
+ union {
+ struct gpio_desc gpio;
+ int id;
+ };
+};
+
+struct atmel_nand_cs {
+ int id;
+ struct atmel_nand_rb rb;
+ struct gpio_desc csgpio;
+ struct {
+ void __iomem *virt;
+ dma_addr_t dma;
+ } io;
+
+ struct atmel_smc_cs_conf smcconf;
+};
+
+struct atmel_nand {
+ struct list_head node;
+ struct udevice *dev;
+ struct nand_chip base;
+ struct atmel_nand_cs *activecs;
+ struct atmel_pmecc_user *pmecc;
+ struct gpio_desc cdgpio;
+ int numcs;
+ struct nand_controller *controller;
+ struct atmel_nand_cs cs[];
+};
+
+static inline struct atmel_nand *to_atmel_nand(struct nand_chip *chip)
+{
+ return container_of(chip, struct atmel_nand, base);
+}
+
+enum atmel_nfc_data_xfer {
+ ATMEL_NFC_NO_DATA,
+ ATMEL_NFC_READ_DATA,
+ ATMEL_NFC_WRITE_DATA,
+};
+
+struct atmel_nfc_op {
+ u8 cs;
+ u8 ncmds;
+ u8 cmds[2];
+ u8 naddrs;
+ u8 addrs[5];
+ enum atmel_nfc_data_xfer data;
+ u32 wait;
+ u32 errors;
+};
+
+struct atmel_nand_controller;
+struct atmel_nand_controller_caps;
+
+struct atmel_nand_controller_ops {
+ int (*probe)(struct udevice *udev,
+ const struct atmel_nand_controller_caps *caps);
+ int (*remove)(struct atmel_nand_controller *nc);
+ void (*nand_init)(struct atmel_nand_controller *nc,
+ struct atmel_nand *nand);
+ int (*ecc_init)(struct nand_chip *chip);
+ int (*setup_data_interface)(struct atmel_nand *nand, int csline,
+ const struct nand_data_interface *conf);
+};
+
+struct atmel_nand_controller_caps {
+ bool has_dma;
+ bool legacy_of_bindings;
+ u32 ale_offs;
+ u32 cle_offs;
+ const char *ebi_csa_regmap_name;
+ const struct atmel_nand_controller_ops *ops;
+};
+
+struct atmel_nand_controller {
+ struct nand_controller base;
+ const struct atmel_nand_controller_caps *caps;
+ struct udevice *dev;
+ struct regmap *smc;
+ struct dma_chan *dmac;
+ struct atmel_pmecc *pmecc;
+ struct list_head chips;
+ struct clk *mck;
+};
+
+static inline struct atmel_nand_controller *
+to_nand_controller(struct nand_controller *ctl)
+{
+ return container_of(ctl, struct atmel_nand_controller, base);
+}
+
+struct atmel_smc_nand_ebi_csa_cfg {
+ u32 offs;
+ u32 nfd0_on_d16;
+};
+
+struct atmel_smc_nand_controller {
+ struct atmel_nand_controller base;
+ struct regmap *ebi_csa_regmap;
+ struct atmel_smc_nand_ebi_csa_cfg *ebi_csa;
+};
+
+static inline struct atmel_smc_nand_controller *
+to_smc_nand_controller(struct nand_controller *ctl)
+{
+ return container_of(to_nand_controller(ctl),
+ struct atmel_smc_nand_controller, base);
+}
+
+struct atmel_hsmc_nand_controller {
+ struct atmel_nand_controller base;
+ struct {
+ struct gen_pool *pool;
+ void __iomem *virt;
+ dma_addr_t dma;
+ } sram;
+ const struct atmel_hsmc_reg_layout *hsmc_layout;
+ struct regmap *io;
+ struct atmel_nfc_op op;
+ struct completion complete;
+ int irq;
+
+ /* Only used when instantiating from legacy DT bindings. */
+ struct clk *clk;
+};
+
+static inline struct atmel_hsmc_nand_controller *
+to_hsmc_nand_controller(struct nand_controller *ctl)
+{
+ return container_of(to_nand_controller(ctl),
+ struct atmel_hsmc_nand_controller, base);
+}
+
+static void pmecc_config_ecc_layout(struct nand_ecclayout *layout,
+ int oobsize, int ecc_len)
+{
+ int i;
+
+ layout->eccbytes = ecc_len;
+
+ /* ECC will occupy the last ecc_len bytes continuously */
+ for (i = 0; i < ecc_len; i++)
+ layout->eccpos[i] = oobsize - ecc_len + i;
+
+ layout->oobfree[0].offset = 2;
+ layout->oobfree[0].length =
+ oobsize - ecc_len - layout->oobfree[0].offset;
+}
+
+static bool atmel_nfc_op_done(struct atmel_nfc_op *op, u32 status)
+{
+ op->errors |= status & ATMEL_HSMC_NFC_SR_ERRORS;
+ op->wait ^= status & op->wait;
+
+ return !op->wait || op->errors;
+}
+
+static int atmel_nfc_wait(struct atmel_hsmc_nand_controller *nc, bool poll,
+ unsigned int timeout_ms)
+{
+ int ret;
+ u32 status;
+
+ if (!timeout_ms)
+ timeout_ms = DEFAULT_TIMEOUT_MS;
+
+ if (poll)
+ ret = regmap_read_poll_timeout(nc->base.smc,
+ ATMEL_HSMC_NFC_SR, status,
+ atmel_nfc_op_done(&nc->op,
+ status),
+ 0, timeout_ms);
+ else
+ return -EOPNOTSUPP;
+
+ if (nc->op.errors & ATMEL_HSMC_NFC_SR_DTOE) {
+ dev_err(nc->base.dev, "Waiting NAND R/B Timeout\n");
+ ret = -ETIMEDOUT;
+ }
+
+ if (nc->op.errors & ATMEL_HSMC_NFC_SR_UNDEF) {
+ dev_err(nc->base.dev, "Access to an undefined area\n");
+ ret = -EIO;
+ }
+
+ if (nc->op.errors & ATMEL_HSMC_NFC_SR_AWB) {
+ dev_err(nc->base.dev, "Access while busy\n");
+ ret = -EIO;
+ }
+
+ if (nc->op.errors & ATMEL_HSMC_NFC_SR_NFCASE) {
+ dev_err(nc->base.dev, "Wrong access size\n");
+ ret = -EIO;
+ }
+
+ return ret;
+}
+
+static void iowrite8_rep(void *addr, const uint8_t *buf, int len)
+{
+ int i;
+
+ for (i = 0; i < len; i++)
+ writeb(buf[i], addr);
+}
+
+static void ioread8_rep(void *addr, uint8_t *buf, int len)
+{
+ int i;
+
+ for (i = 0; i < len; i++)
+ buf[i] = readb(addr);
+}
+
+static void ioread16_rep(void *addr, void *buf, int len)
+{
+ int i;
+ u16 *p = (u16 *)buf;
+
+ for (i = 0; i < len; i++)
+ p[i] = readw(addr);
+}
+
+static void iowrite16_rep(void *addr, const void *buf, int len)
+{
+ int i;
+ u16 *p = (u16 *)buf;
+
+ for (i = 0; i < len; i++)
+ writew(p[i], addr);
+}
+
+static u8 atmel_nand_read_byte(struct mtd_info *mtd)
+{
+ struct nand_chip *chip = mtd_to_nand(mtd);
+ struct atmel_nand *nand = to_atmel_nand(chip);
+
+ return ioread8(nand->activecs->io.virt);
+}
+
+static void atmel_nand_write_byte(struct mtd_info *mtd, u8 byte)
+{
+ struct nand_chip *chip = mtd_to_nand(mtd);
+ struct atmel_nand *nand = to_atmel_nand(chip);
+
+ if (chip->options & NAND_BUSWIDTH_16)
+ iowrite16(byte | (byte << 8), nand->activecs->io.virt);
+ else
+ iowrite8(byte, nand->activecs->io.virt);
+}
+
+static void atmel_nand_read_buf(struct mtd_info *mtd, u8 *buf, int len)
+{
+ struct nand_chip *chip = mtd_to_nand(mtd);
+ struct atmel_nand *nand = to_atmel_nand(chip);
+
+ if (chip->options & NAND_BUSWIDTH_16)
+ ioread16_rep(nand->activecs->io.virt, buf, len / 2);
+ else
+ ioread8_rep(nand->activecs->io.virt, buf, len);
+}
+
+static void atmel_nand_write_buf(struct mtd_info *mtd, const u8 *buf, int len)
+{
+ struct nand_chip *chip = mtd_to_nand(mtd);
+ struct atmel_nand *nand = to_atmel_nand(chip);
+
+ if (chip->options & NAND_BUSWIDTH_16)
+ iowrite16_rep(nand->activecs->io.virt, buf, len / 2);
+ else
+ iowrite8_rep(nand->activecs->io.virt, buf, len);
+}
+
+static int atmel_nand_dev_ready(struct mtd_info *mtd)
+{
+ struct nand_chip *chip = mtd_to_nand(mtd);
+ struct atmel_nand *nand = to_atmel_nand(chip);
+
+ return dm_gpio_get_value(&nand->activecs->rb.gpio);
+}
+
+static void atmel_nand_select_chip(struct mtd_info *mtd, int cs)
+{
+ struct nand_chip *chip = mtd_to_nand(mtd);
+ struct atmel_nand *nand = to_atmel_nand(chip);
+
+ if (cs < 0 || cs >= nand->numcs) {
+ nand->activecs = NULL;
+ chip->dev_ready = NULL;
+ return;
+ }
+
+ nand->activecs = &nand->cs[cs];
+
+ if (nand->activecs->rb.type == ATMEL_NAND_GPIO_RB)
+ chip->dev_ready = atmel_nand_dev_ready;
+}
+
+static int atmel_hsmc_nand_dev_ready(struct mtd_info *mtd)
+{
+ struct nand_chip *chip = mtd_to_nand(mtd);
+ struct atmel_nand *nand = to_atmel_nand(chip);
+ struct atmel_hsmc_nand_controller *nc;
+ u32 status;
+
+ nc = to_hsmc_nand_controller(nand->controller);
+
+ regmap_read(nc->base.smc, ATMEL_HSMC_NFC_SR, &status);
+
+ return status & ATMEL_HSMC_NFC_SR_RBEDGE(nand->activecs->rb.id);
+}
+
+static void atmel_hsmc_nand_select_chip(struct mtd_info *mtd, int cs)
+{
+ struct nand_chip *chip = mtd_to_nand(mtd);
+ struct atmel_nand *nand = to_atmel_nand(chip);
+ struct atmel_hsmc_nand_controller *nc;
+
+ nc = to_hsmc_nand_controller(nand->controller);
+
+ atmel_nand_select_chip(mtd, cs);
+
+ if (!nand->activecs) {
+ regmap_write(nc->base.smc, ATMEL_HSMC_NFC_CTRL,
+ ATMEL_HSMC_NFC_CTRL_DIS);
+ return;
+ }
+
+ if (nand->activecs->rb.type == ATMEL_NAND_NATIVE_RB)
+ chip->dev_ready = atmel_hsmc_nand_dev_ready;
+
+ regmap_update_bits(nc->base.smc, ATMEL_HSMC_NFC_CFG,
+ ATMEL_HSMC_NFC_CFG_PAGESIZE_MASK |
+ ATMEL_HSMC_NFC_CFG_SPARESIZE_MASK |
+ ATMEL_HSMC_NFC_CFG_RSPARE |
+ ATMEL_HSMC_NFC_CFG_WSPARE,
+ ATMEL_HSMC_NFC_CFG_PAGESIZE(mtd->writesize) |
+ ATMEL_HSMC_NFC_CFG_SPARESIZE(mtd->oobsize) |
+ ATMEL_HSMC_NFC_CFG_RSPARE);
+ regmap_write(nc->base.smc, ATMEL_HSMC_NFC_CTRL,
+ ATMEL_HSMC_NFC_CTRL_EN);
+}
+
+static int atmel_nfc_exec_op(struct atmel_hsmc_nand_controller *nc, bool poll)
+{
+ u8 *addrs = nc->op.addrs;
+ unsigned int op = 0;
+ u32 addr, val;
+ int i, ret;
+
+ nc->op.wait = ATMEL_HSMC_NFC_SR_CMDDONE;
+
+ for (i = 0; i < nc->op.ncmds; i++)
+ op |= ATMEL_NFC_CMD(i, nc->op.cmds[i]);
+
+ if (nc->op.naddrs == ATMEL_NFC_MAX_ADDR_CYCLES)
+ regmap_write(nc->base.smc, ATMEL_HSMC_NFC_ADDR, *addrs++);
+
+ op |= ATMEL_NFC_CSID(nc->op.cs) |
+ ATMEL_NFC_ACYCLE(nc->op.naddrs);
+
+ if (nc->op.ncmds > 1)
+ op |= ATMEL_NFC_VCMD2;
+
+ addr = addrs[0] | (addrs[1] << 8) | (addrs[2] << 16) |
+ (addrs[3] << 24);
+
+ if (nc->op.data != ATMEL_NFC_NO_DATA) {
+ op |= ATMEL_NFC_DATAEN;
+ nc->op.wait |= ATMEL_HSMC_NFC_SR_XFRDONE;
+
+ if (nc->op.data == ATMEL_NFC_WRITE_DATA)
+ op |= ATMEL_NFC_NFCWR;
+ }
+
+ /* Clear all flags. */
+ regmap_read(nc->base.smc, ATMEL_HSMC_NFC_SR, &val);
+
+ /* Send the command. */
+ regmap_write(nc->io, op, addr);
+
+ ret = atmel_nfc_wait(nc, poll, 0);
+ if (ret)
+ dev_err(nc->base.dev,
+ "Failed to send NAND command (err = %d)!",
+ ret);
+
+ /* Reset the op state. */
+ memset(&nc->op, 0, sizeof(nc->op));
+
+ return ret;
+}
+
+static void atmel_hsmc_nand_cmd_ctrl(struct mtd_info *mtd, int dat,
+ unsigned int ctrl)
+{
+ struct nand_chip *chip = mtd_to_nand(mtd);
+ struct atmel_nand *nand = to_atmel_nand(chip);
+ struct atmel_hsmc_nand_controller *nc;
+
+ nc = to_hsmc_nand_controller(nand->controller);
+
+ if (ctrl & NAND_ALE) {
+ if (nc->op.naddrs == ATMEL_NFC_MAX_ADDR_CYCLES)
+ return;
+
+ nc->op.addrs[nc->op.naddrs++] = dat;
+ } else if (ctrl & NAND_CLE) {
+ if (nc->op.ncmds > 1)
+ return;
+
+ nc->op.cmds[nc->op.ncmds++] = dat;
+ }
+
+ if (dat == NAND_CMD_NONE) {
+ nc->op.cs = nand->activecs->id;
+ atmel_nfc_exec_op(nc, true);
+ }
+}
+
+static void atmel_nand_cmd_ctrl(struct mtd_info *mtd, int cmd,
+ unsigned int ctrl)
+{
+ struct nand_chip *chip = mtd_to_nand(mtd);
+ struct atmel_nand *nand = to_atmel_nand(chip);
+ struct atmel_nand_controller *nc;
+
+ nc = to_nand_controller(nand->controller);
+
+ if ((ctrl & NAND_CTRL_CHANGE) &&
+ dm_gpio_is_valid(&nand->activecs->csgpio)) {
+ if (ctrl & NAND_NCE)
+ dm_gpio_set_value(&nand->activecs->csgpio, 0);
+ else
+ dm_gpio_set_value(&nand->activecs->csgpio, 1);
+ }
+
+ if (ctrl & NAND_ALE)
+ writeb(cmd, nand->activecs->io.virt + nc->caps->ale_offs);
+ else if (ctrl & NAND_CLE)
+ writeb(cmd, nand->activecs->io.virt + nc->caps->cle_offs);
+}
+
+static void atmel_nfc_copy_to_sram(struct nand_chip *chip, const u8 *buf,
+ bool oob_required)
+{
+ struct mtd_info *mtd = nand_to_mtd(chip);
+ struct atmel_nand *nand = to_atmel_nand(chip);
+ struct atmel_hsmc_nand_controller *nc;
+ int ret = -EIO;
+
+ nc = to_hsmc_nand_controller(nand->controller);
+
+ if (ret)
+ memcpy_toio(nc->sram.virt, buf, mtd->writesize);
+
+ if (oob_required)
+ memcpy_toio(nc->sram.virt + mtd->writesize, chip->oob_poi,
+ mtd->oobsize);
+}
+
+static void atmel_nfc_copy_from_sram(struct nand_chip *chip, u8 *buf,
+ bool oob_required)
+{
+ struct mtd_info *mtd = nand_to_mtd(chip);
+ struct atmel_nand *nand = to_atmel_nand(chip);
+ struct atmel_hsmc_nand_controller *nc;
+ int ret = -EIO;
+
+ nc = to_hsmc_nand_controller(nand->controller);
+
+ if (ret)
+ memcpy_fromio(buf, nc->sram.virt, mtd->writesize);
+
+ if (oob_required)
+ memcpy_fromio(chip->oob_poi, nc->sram.virt + mtd->writesize,
+ mtd->oobsize);
+}
+
+static void atmel_nfc_set_op_addr(struct nand_chip *chip, int page, int column)
+{
+ struct mtd_info *mtd = nand_to_mtd(chip);
+ struct atmel_nand *nand = to_atmel_nand(chip);
+ struct atmel_hsmc_nand_controller *nc;
+
+ nc = to_hsmc_nand_controller(nand->controller);
+
+ if (column >= 0) {
+ nc->op.addrs[nc->op.naddrs++] = column;
+
+ /*
+ * 2 address cycles for the column offset on large page NANDs.
+ */
+ if (mtd->writesize > 512)
+ nc->op.addrs[nc->op.naddrs++] = column >> 8;
+ }
+
+ if (page >= 0) {
+ nc->op.addrs[nc->op.naddrs++] = page;
+ nc->op.addrs[nc->op.naddrs++] = page >> 8;
+
+ if (chip->options & NAND_ROW_ADDR_3)
+ nc->op.addrs[nc->op.naddrs++] = page >> 16;
+ }
+}
+
+static int atmel_nand_pmecc_enable(struct nand_chip *chip, int op, bool raw)
+{
+ struct atmel_nand *nand = to_atmel_nand(chip);
+ struct atmel_nand_controller *nc;
+ int ret;
+
+ nc = to_nand_controller(nand->controller);
+
+ if (raw)
+ return 0;
+
+ ret = atmel_pmecc_enable(nand->pmecc, op);
+ if (ret)
+ dev_err(nc->dev,
+ "Failed to enable ECC engine (err = %d)\n", ret);
+
+ return ret;
+}
+
+static void atmel_nand_pmecc_disable(struct nand_chip *chip, bool raw)
+{
+ struct atmel_nand *nand = to_atmel_nand(chip);
+
+ if (!raw)
+ atmel_pmecc_disable(nand->pmecc);
+}
+
+static int atmel_nand_pmecc_generate_eccbytes(struct nand_chip *chip, bool raw)
+{
+ struct atmel_nand *nand = to_atmel_nand(chip);
+ struct mtd_info *mtd = nand_to_mtd(chip);
+ struct atmel_nand_controller *nc;
+ struct mtd_oob_region oobregion;
+ void *eccbuf;
+ int ret, i;
+
+ nc = to_nand_controller(nand->controller);
+
+ if (raw)
+ return 0;
+
+ ret = atmel_pmecc_wait_rdy(nand->pmecc);
+ if (ret) {
+ dev_err(nc->dev,
+ "Failed to transfer NAND page data (err = %d)\n",
+ ret);
+ return ret;
+ }
+
+ mtd_ooblayout_ecc(mtd, 0, &oobregion);
+ eccbuf = chip->oob_poi + oobregion.offset;
+
+ for (i = 0; i < chip->ecc.steps; i++) {
+ atmel_pmecc_get_generated_eccbytes(nand->pmecc, i,
+ eccbuf);
+ eccbuf += chip->ecc.bytes;
+ }
+
+ return 0;
+}
+
+static int atmel_nand_pmecc_correct_data(struct nand_chip *chip, void *buf,
+ bool raw)
+{
+ struct atmel_nand *nand = to_atmel_nand(chip);
+ struct mtd_info *mtd = nand_to_mtd(chip);
+ struct atmel_nand_controller *nc;
+ struct mtd_oob_region oobregion;
+ int ret, i, max_bitflips = 0;
+ void *databuf, *eccbuf;
+
+ nc = to_nand_controller(nand->controller);
+
+ if (raw)
+ return 0;
+
+ ret = atmel_pmecc_wait_rdy(nand->pmecc);
+ if (ret) {
+ dev_err(nc->dev,
+ "Failed to read NAND page data (err = %d)\n", ret);
+ return ret;
+ }
+
+ mtd_ooblayout_ecc(mtd, 0, &oobregion);
+ eccbuf = chip->oob_poi + oobregion.offset;
+ databuf = buf;
+
+ for (i = 0; i < chip->ecc.steps; i++) {
+ ret = atmel_pmecc_correct_sector(nand->pmecc, i, databuf,
+ eccbuf);
+ if (ret < 0 && !atmel_pmecc_correct_erased_chunks(nand->pmecc))
+ ret = nand_check_erased_ecc_chunk(databuf,
+ chip->ecc.size,
+ eccbuf,
+ chip->ecc.bytes,
+ NULL, 0,
+ chip->ecc.strength);
+
+ if (ret >= 0)
+ max_bitflips = max(ret, max_bitflips);
+ else
+ mtd->ecc_stats.failed++;
+
+ databuf += chip->ecc.size;
+ eccbuf += chip->ecc.bytes;
+ }
+
+ return max_bitflips;
+}
+
+static int atmel_nand_pmecc_write_pg(struct nand_chip *chip, const u8 *buf,
+ bool oob_required, int page, bool raw)
+{
+ struct mtd_info *mtd = nand_to_mtd(chip);
+ struct atmel_nand *nand = to_atmel_nand(chip);
+ int ret;
+
+ nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+
+ ret = atmel_nand_pmecc_enable(chip, NAND_ECC_WRITE, raw);
+ if (ret)
+ return ret;
+
+ atmel_nand_write_buf(mtd, buf, mtd->writesize);
+
+ ret = atmel_nand_pmecc_generate_eccbytes(chip, raw);
+ if (ret) {
+ atmel_pmecc_disable(nand->pmecc);
+ return ret;
+ }
+
+ atmel_nand_pmecc_disable(chip, raw);
+
+ atmel_nand_write_buf(mtd, chip->oob_poi, mtd->oobsize);
+
+ return nand_prog_page_end_op(chip);
+}
+
+static int atmel_nand_pmecc_write_page(struct mtd_info *mtd,
+ struct nand_chip *chip, const u8 *buf,
+ int oob_required, int page)
+{
+ return atmel_nand_pmecc_write_pg(chip, buf, oob_required, page, false);
+}
+
+static int atmel_nand_pmecc_write_page_raw(struct mtd_info *mtd,
+ struct nand_chip *chip,
+ const u8 *buf, int oob_required,
+ int page)
+{
+ return atmel_nand_pmecc_write_pg(chip, buf, oob_required, page, true);
+}
+
+static int atmel_nand_pmecc_read_pg(struct nand_chip *chip, u8 *buf,
+ bool oob_required, int page, bool raw)
+{
+ struct mtd_info *mtd = nand_to_mtd(chip);
+ int ret;
+
+ nand_read_page_op(chip, page, 0, NULL, 0);
+
+ ret = atmel_nand_pmecc_enable(chip, NAND_ECC_READ, raw);
+ if (ret)
+ return ret;
+
+ atmel_nand_read_buf(mtd, buf, mtd->writesize);
+ atmel_nand_read_buf(mtd, chip->oob_poi, mtd->oobsize);
+
+ ret = atmel_nand_pmecc_correct_data(chip, buf, raw);
+
+ atmel_nand_pmecc_disable(chip, raw);
+
+ return ret;
+}
+
+static int atmel_nand_pmecc_read_page(struct mtd_info *mtd,
+ struct nand_chip *chip, u8 *buf,
+ int oob_required, int page)
+{
+ return atmel_nand_pmecc_read_pg(chip, buf, oob_required, page, false);
+}
+
+static int atmel_nand_pmecc_read_page_raw(struct mtd_info *mtd,
+ struct nand_chip *chip, u8 *buf,
+ int oob_required, int page)
+{
+ return atmel_nand_pmecc_read_pg(chip, buf, oob_required, page, true);
+}
+
+static int atmel_hsmc_nand_pmecc_write_pg(struct nand_chip *chip,
+ const u8 *buf, bool oob_required,
+ int page, bool raw)
+{
+ struct mtd_info *mtd = nand_to_mtd(chip);
+ struct atmel_nand *nand = to_atmel_nand(chip);
+ struct atmel_hsmc_nand_controller *nc;
+ int ret, status;
+
+ nc = to_hsmc_nand_controller(nand->controller);
+
+ atmel_nfc_copy_to_sram(chip, buf, false);
+
+ nc->op.cmds[0] = NAND_CMD_SEQIN;
+ nc->op.ncmds = 1;
+ atmel_nfc_set_op_addr(chip, page, 0x0);
+ nc->op.cs = nand->activecs->id;
+ nc->op.data = ATMEL_NFC_WRITE_DATA;
+
+ ret = atmel_nand_pmecc_enable(chip, NAND_ECC_WRITE, raw);
+ if (ret)
+ return ret;
+
+ ret = atmel_nfc_exec_op(nc, true);
+ if (ret) {
+ atmel_nand_pmecc_disable(chip, raw);
+ dev_err(nc->base.dev,
+ "Failed to transfer NAND page data (err = %d)\n",
+ ret);
+ return ret;
+ }
+
+ ret = atmel_nand_pmecc_generate_eccbytes(chip, raw);
+
+ atmel_nand_pmecc_disable(chip, raw);
+
+ if (ret)
+ return ret;
+
+ atmel_nand_write_buf(mtd, chip->oob_poi, mtd->oobsize);
+
+ nc->op.cmds[0] = NAND_CMD_PAGEPROG;
+ nc->op.ncmds = 1;
+ nc->op.cs = nand->activecs->id;
+ ret = atmel_nfc_exec_op(nc, true);
+ if (ret)
+ dev_err(nc->base.dev, "Failed to program NAND page (err = %d)\n",
+ ret);
+
+ status = chip->waitfunc(mtd, chip);
+ if (status & NAND_STATUS_FAIL)
+ return -EIO;
+
+ return ret;
+}
+
+static int
+atmel_hsmc_nand_pmecc_write_page(struct mtd_info *mtd, struct nand_chip *chip,
+ const u8 *buf, int oob_required,
+ int page)
+{
+ return atmel_hsmc_nand_pmecc_write_pg(chip, buf, oob_required, page,
+ false);
+}
+
+static int
+atmel_hsmc_nand_pmecc_write_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
+ const u8 *buf,
+ int oob_required, int page)
+{
+ return atmel_hsmc_nand_pmecc_write_pg(chip, buf, oob_required, page,
+ true);
+}
+
+static int atmel_hsmc_nand_pmecc_read_pg(struct nand_chip *chip, u8 *buf,
+ bool oob_required, int page,
+ bool raw)
+{
+ struct mtd_info *mtd = nand_to_mtd(chip);
+ struct atmel_nand *nand = to_atmel_nand(chip);
+ struct atmel_hsmc_nand_controller *nc;
+ int ret;
+
+ nc = to_hsmc_nand_controller(nand->controller);
+
+ /*
+ * Optimized read page accessors only work when the NAND R/B pin is
+ * connected to a native SoC R/B pin. If that's not the case, fallback
+ * to the non-optimized one.
+ */
+ if (nand->activecs->rb.type != ATMEL_NAND_NATIVE_RB) {
+ nand_read_page_op(chip, page, 0, NULL, 0);
+
+ return atmel_nand_pmecc_read_pg(chip, buf, oob_required, page,
+ raw);
+ }
+
+ nc->op.cmds[nc->op.ncmds++] = NAND_CMD_READ0;
+
+ if (mtd->writesize > 512)
+ nc->op.cmds[nc->op.ncmds++] = NAND_CMD_READSTART;
+
+ atmel_nfc_set_op_addr(chip, page, 0x0);
+ nc->op.cs = nand->activecs->id;
+ nc->op.data = ATMEL_NFC_READ_DATA;
+
+ ret = atmel_nand_pmecc_enable(chip, NAND_ECC_READ, raw);
+ if (ret)
+ return ret;
+
+ ret = atmel_nfc_exec_op(nc, true);
+ if (ret) {
+ atmel_nand_pmecc_disable(chip, raw);
+ dev_err(nc->base.dev,
+ "Failed to load NAND page data (err = %d)\n",
+ ret);
+ return ret;
+ }
+
+ atmel_nfc_copy_from_sram(chip, buf, true);
+
+ ret = atmel_nand_pmecc_correct_data(chip, buf, raw);
+
+ atmel_nand_pmecc_disable(chip, raw);
+
+ return ret;
+}
+
+static int atmel_hsmc_nand_pmecc_read_page(struct mtd_info *mtd,
+ struct nand_chip *chip, u8 *buf,
+ int oob_required, int page)
+{
+ return atmel_hsmc_nand_pmecc_read_pg(chip, buf, oob_required, page,
+ false);
+}
+
+static int atmel_hsmc_nand_pmecc_read_page_raw(struct mtd_info *mtd,
+ struct nand_chip *chip,
+ u8 *buf, int oob_required,
+ int page)
+{
+ return atmel_hsmc_nand_pmecc_read_pg(chip, buf, oob_required, page,
+ true);
+}
+
+static int nand_ooblayout_ecc_lp(struct mtd_info *mtd, int section,
+ struct mtd_oob_region *oobregion)
+{
+ struct nand_chip *chip = mtd_to_nand(mtd);
+ struct nand_ecc_ctrl *ecc = &chip->ecc;
+
+ if (section || !ecc->total)
+ return -ERANGE;
+
+ oobregion->length = ecc->total;
+ oobregion->offset = mtd->oobsize - oobregion->length;
+
+ return 0;
+}
+
+static int nand_ooblayout_free_lp(struct mtd_info *mtd, int section,
+ struct mtd_oob_region *oobregion)
+{
+ struct nand_chip *chip = mtd_to_nand(mtd);
+ struct nand_ecc_ctrl *ecc = &chip->ecc;
+
+ if (section)
+ return -ERANGE;
+
+ oobregion->length = mtd->oobsize - ecc->total - 2;
+ oobregion->offset = 2;
+
+ return 0;
+}
+
+static const struct mtd_ooblayout_ops nand_ooblayout_lp_ops = {
+ .ecc = nand_ooblayout_ecc_lp,
+ .rfree = nand_ooblayout_free_lp,
+};
+
+const struct mtd_ooblayout_ops *nand_get_large_page_ooblayout(void)
+{
+ return &nand_ooblayout_lp_ops;
+}
+
+static int atmel_nand_pmecc_init(struct nand_chip *chip)
+{
+ struct mtd_info *mtd = nand_to_mtd(chip);
+ struct atmel_nand *nand = to_atmel_nand(chip);
+ struct atmel_nand_controller *nc;
+ struct atmel_pmecc_user_req req;
+
+ nc = to_nand_controller(nand->controller);
+
+ if (!nc->pmecc) {
+ dev_err(nc->dev, "HW ECC not supported\n");
+ return -EOPNOTSUPP;
+ }
+
+ if (nc->caps->legacy_of_bindings) {
+ u32 val;
+
+ if (!ofnode_read_u32(nc->dev->node_, "atmel,pmecc-cap", &val))
+ chip->ecc.strength = val;
+
+ if (!ofnode_read_u32(nc->dev->node_,
+ "atmel,pmecc-sector-size",
+ &val))
+ chip->ecc.size = val;
+ }
+
+ if (chip->ecc.options & NAND_ECC_MAXIMIZE)
+ req.ecc.strength = ATMEL_PMECC_MAXIMIZE_ECC_STRENGTH;
+ else if (chip->ecc.strength)
+ req.ecc.strength = chip->ecc.strength;
+ else
+ req.ecc.strength = ATMEL_PMECC_MAXIMIZE_ECC_STRENGTH;
+
+ if (chip->ecc.size)
+ req.ecc.sectorsize = chip->ecc.size;
+ else
+ req.ecc.sectorsize = ATMEL_PMECC_SECTOR_SIZE_AUTO;
+
+ req.pagesize = mtd->writesize;
+ req.oobsize = mtd->oobsize;
+
+ if (mtd->writesize <= 512) {
+ req.ecc.bytes = 4;
+ req.ecc.ooboffset = 0;
+ } else {
+ req.ecc.bytes = mtd->oobsize - 2;
+ req.ecc.ooboffset = ATMEL_PMECC_OOBOFFSET_AUTO;
+ }
+
+ nand->pmecc = atmel_pmecc_create_user(nc->pmecc, &req);
+ if (IS_ERR(nand->pmecc))
+ return PTR_ERR(nand->pmecc);
+
+ chip->ecc.algo = NAND_ECC_BCH;
+ chip->ecc.size = req.ecc.sectorsize;
+ chip->ecc.bytes = req.ecc.bytes / req.ecc.nsectors;
+ chip->ecc.strength = req.ecc.strength;
+
+ chip->options |= NAND_NO_SUBPAGE_WRITE;
+
+ mtd_set_ooblayout(mtd, nand_get_large_page_ooblayout());
+ pmecc_config_ecc_layout(&atmel_pmecc_oobinfo,
+ mtd->oobsize,
+ chip->ecc.bytes);
+ chip->ecc.layout = &atmel_pmecc_oobinfo;
+
+ return 0;
+}
+
+static int atmel_nand_ecc_init(struct nand_chip *chip)
+{
+ struct atmel_nand_controller *nc;
+ struct atmel_nand *nand = to_atmel_nand(chip);
+ int ret;
+
+ nc = to_nand_controller(nand->controller);
+
+ switch (chip->ecc.mode) {
+ case NAND_ECC_NONE:
+ case NAND_ECC_SOFT:
+ /*
+ * Nothing to do, the core will initialize everything for us.
+ */
+ break;
+
+ case NAND_ECC_HW:
+ ret = atmel_nand_pmecc_init(chip);
+ if (ret)
+ return ret;
+
+ chip->ecc.read_page = atmel_nand_pmecc_read_page;
+ chip->ecc.write_page = atmel_nand_pmecc_write_page;
+ chip->ecc.read_page_raw = atmel_nand_pmecc_read_page_raw;
+ chip->ecc.write_page_raw = atmel_nand_pmecc_write_page_raw;
+ break;
+
+ default:
+ /* Other modes are not supported. */
+ dev_err(nc->dev, "Unsupported ECC mode: %d\n",
+ chip->ecc.mode);
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static int atmel_hsmc_nand_ecc_init(struct nand_chip *chip)
+{
+ int ret;
+
+ ret = atmel_nand_ecc_init(chip);
+ if (ret)
+ return ret;
+
+ if (chip->ecc.mode != NAND_ECC_HW)
+ return 0;
+
+ /* Adjust the ECC operations for the HSMC IP. */
+ chip->ecc.read_page = atmel_hsmc_nand_pmecc_read_page;
+ chip->ecc.write_page = atmel_hsmc_nand_pmecc_write_page;
+ chip->ecc.read_page_raw = atmel_hsmc_nand_pmecc_read_page_raw;
+ chip->ecc.write_page_raw = atmel_hsmc_nand_pmecc_write_page_raw;
+
+ return 0;
+}
+
+static int atmel_smc_nand_prepare_smcconf(struct atmel_nand *nand,
+ const struct nand_data_interface *conf,
+ struct atmel_smc_cs_conf *smcconf)
+{
+ u32 ncycles, totalcycles, timeps, mckperiodps;
+ struct atmel_nand_controller *nc;
+ int ret;
+
+ nc = to_nand_controller(nand->controller);
+
+ /* DDR interface not supported. */
+ if (conf->type != NAND_SDR_IFACE)
+ return -EOPNOTSUPP;
+
+ /*
+ * tRC < 30ns implies EDO mode. This controller does not support this
+ * mode.
+ */
+ if (conf->timings.sdr.tRC_min < 30000)
+ return -EOPNOTSUPP;
+
+ atmel_smc_cs_conf_init(smcconf);
+
+ mckperiodps = NSEC_PER_SEC / clk_get_rate(nc->mck);
+ mckperiodps *= 1000;
+
+ /*
+ * Set write pulse timing. This one is easy to extract:
+ *
+ * NWE_PULSE = tWP
+ */
+ ncycles = DIV_ROUND_UP(conf->timings.sdr.tWP_min, mckperiodps);
+ totalcycles = ncycles;
+ ret = atmel_smc_cs_conf_set_pulse(smcconf, ATMEL_SMC_NWE_SHIFT,
+ ncycles);
+ if (ret)
+ return ret;
+
+ /*
+ * The write setup timing depends on the operation done on the NAND.
+ * All operations goes through the same data bus, but the operation
+ * type depends on the address we are writing to (ALE/CLE address
+ * lines).
+ * Since we have no way to differentiate the different operations at
+ * the SMC level, we must consider the worst case (the biggest setup
+ * time among all operation types):
+ *
+ * NWE_SETUP = max(tCLS, tCS, tALS, tDS) - NWE_PULSE
+ */
+ timeps = max3(conf->timings.sdr.tCLS_min, conf->timings.sdr.tCS_min,
+ conf->timings.sdr.tALS_min);
+ timeps = max(timeps, conf->timings.sdr.tDS_min);
+ ncycles = DIV_ROUND_UP(timeps, mckperiodps);
+ ncycles = ncycles > totalcycles ? ncycles - totalcycles : 0;
+ totalcycles += ncycles;
+ ret = atmel_smc_cs_conf_set_setup(smcconf, ATMEL_SMC_NWE_SHIFT,
+ ncycles);
+ if (ret)
+ return ret;
+
+ /*
+ * As for the write setup timing, the write hold timing depends on the
+ * operation done on the NAND:
+ *
+ * NWE_HOLD = max(tCLH, tCH, tALH, tDH, tWH)
+ */
+ timeps = max3(conf->timings.sdr.tCLH_min, conf->timings.sdr.tCH_min,
+ conf->timings.sdr.tALH_min);
+ timeps = max3(timeps, conf->timings.sdr.tDH_min,
+ conf->timings.sdr.tWH_min);
+ ncycles = DIV_ROUND_UP(timeps, mckperiodps);
+ totalcycles += ncycles;
+
+ /*
+ * The write cycle timing is directly matching tWC, but is also
+ * dependent on the other timings on the setup and hold timings we
+ * calculated earlier, which gives:
+ *
+ * NWE_CYCLE = max(tWC, NWE_SETUP + NWE_PULSE + NWE_HOLD)
+ */
+ ncycles = DIV_ROUND_UP(conf->timings.sdr.tWC_min, mckperiodps);
+ ncycles = max(totalcycles, ncycles);
+ ret = atmel_smc_cs_conf_set_cycle(smcconf, ATMEL_SMC_NWE_SHIFT,
+ ncycles);
+ if (ret)
+ return ret;
+
+ /*
+ * We don't want the CS line to be toggled between each byte/word
+ * transfer to the NAND. The only way to guarantee that is to have the
+ * NCS_{WR,RD}_{SETUP,HOLD} timings set to 0, which in turn means:
+ *
+ * NCS_WR_PULSE = NWE_CYCLE
+ */
+ ret = atmel_smc_cs_conf_set_pulse(smcconf, ATMEL_SMC_NCS_WR_SHIFT,
+ ncycles);
+ if (ret)
+ return ret;
+
+ /*
+ * As for the write setup timing, the read hold timing depends on the
+ * operation done on the NAND:
+ *
+ * NRD_HOLD = max(tREH, tRHOH)
+ */
+ timeps = max(conf->timings.sdr.tREH_min, conf->timings.sdr.tRHOH_min);
+ ncycles = DIV_ROUND_UP(timeps, mckperiodps);
+ totalcycles = ncycles;
+
+ /*
+ * TDF = tRHZ - NRD_HOLD
+ */
+ ncycles = DIV_ROUND_UP(conf->timings.sdr.tRHZ_max, mckperiodps);
+ ncycles -= totalcycles;
+
+ /*
+ * In ONFI 4.0 specs, tRHZ has been increased to support EDO NANDs and
+ * we might end up with a config that does not fit in the TDF field.
+ * Just take the max value in this case and hope that the NAND is more
+ * tolerant than advertised.
+ */
+ if (ncycles > ATMEL_SMC_MODE_TDF_MAX)
+ ncycles = ATMEL_SMC_MODE_TDF_MAX;
+ else if (ncycles < ATMEL_SMC_MODE_TDF_MIN)
+ ncycles = ATMEL_SMC_MODE_TDF_MIN;
+
+ smcconf->mode |= ATMEL_SMC_MODE_TDF(ncycles) |
+ ATMEL_SMC_MODE_TDFMODE_OPTIMIZED;
+
+ /*
+ * Read pulse timing directly matches tRP:
+ *
+ * NRD_PULSE = tRP
+ */
+ ncycles = DIV_ROUND_UP(conf->timings.sdr.tRP_min, mckperiodps);
+ totalcycles += ncycles;
+ ret = atmel_smc_cs_conf_set_pulse(smcconf, ATMEL_SMC_NRD_SHIFT,
+ ncycles);
+ if (ret)
+ return ret;
+
+ /*
+ * The write cycle timing is directly matching tWC, but is also
+ * dependent on the setup and hold timings we calculated earlier,
+ * which gives:
+ *
+ * NRD_CYCLE = max(tRC, NRD_PULSE + NRD_HOLD)
+ *
+ * NRD_SETUP is always 0.
+ */
+ ncycles = DIV_ROUND_UP(conf->timings.sdr.tRC_min, mckperiodps);
+ ncycles = max(totalcycles, ncycles);
+ ret = atmel_smc_cs_conf_set_cycle(smcconf, ATMEL_SMC_NRD_SHIFT,
+ ncycles);
+ if (ret)
+ return ret;
+
+ /*
+ * We don't want the CS line to be toggled between each byte/word
+ * transfer from the NAND. The only way to guarantee that is to have
+ * the NCS_{WR,RD}_{SETUP,HOLD} timings set to 0, which in turn means:
+ *
+ * NCS_RD_PULSE = NRD_CYCLE
+ */
+ ret = atmel_smc_cs_conf_set_pulse(smcconf, ATMEL_SMC_NCS_RD_SHIFT,
+ ncycles);
+ if (ret)
+ return ret;
+
+ /* Txxx timings are directly matching tXXX ones. */
+ ncycles = DIV_ROUND_UP(conf->timings.sdr.tCLR_min, mckperiodps);
+ ret = atmel_smc_cs_conf_set_timing(smcconf,
+ ATMEL_HSMC_TIMINGS_TCLR_SHIFT,
+ ncycles);
+ if (ret)
+ return ret;
+
+ ncycles = DIV_ROUND_UP(conf->timings.sdr.tADL_min, mckperiodps);
+ ret = atmel_smc_cs_conf_set_timing(smcconf,
+ ATMEL_HSMC_TIMINGS_TADL_SHIFT,
+ ncycles);
+ /*
+ * Version 4 of the ONFI spec mandates that tADL be at least 400
+ * nanoseconds, but, depending on the master clock rate, 400 ns may not
+ * fit in the tADL field of the SMC reg. We need to relax the check and
+ * accept the -ERANGE return code.
+ *
+ * Note that previous versions of the ONFI spec had a lower tADL_min
+ * (100 or 200 ns). It's not clear why this timing constraint got
+ * increased but it seems most NANDs are fine with values lower than
+ * 400ns, so we should be safe.
+ */
+ if (ret && ret != -ERANGE)
+ return ret;
+
+ ncycles = DIV_ROUND_UP(conf->timings.sdr.tAR_min, mckperiodps);
+ ret = atmel_smc_cs_conf_set_timing(smcconf,
+ ATMEL_HSMC_TIMINGS_TAR_SHIFT,
+ ncycles);
+ if (ret)
+ return ret;
+
+ ncycles = DIV_ROUND_UP(conf->timings.sdr.tRR_min, mckperiodps);
+ ret = atmel_smc_cs_conf_set_timing(smcconf,
+ ATMEL_HSMC_TIMINGS_TRR_SHIFT,
+ ncycles);
+ if (ret)
+ return ret;
+
+ ncycles = DIV_ROUND_UP(conf->timings.sdr.tWB_max, mckperiodps);
+ ret = atmel_smc_cs_conf_set_timing(smcconf,
+ ATMEL_HSMC_TIMINGS_TWB_SHIFT,
+ ncycles);
+ if (ret)
+ return ret;
+
+ /* Attach the CS line to the NFC logic. */
+ smcconf->timings |= ATMEL_HSMC_TIMINGS_NFSEL;
+
+ /* Set the appropriate data bus width. */
+ if (nand->base.options & NAND_BUSWIDTH_16)
+ smcconf->mode |= ATMEL_SMC_MODE_DBW_16;
+
+ /* Operate in NRD/NWE READ/WRITEMODE. */
+ smcconf->mode |= ATMEL_SMC_MODE_READMODE_NRD |
+ ATMEL_SMC_MODE_WRITEMODE_NWE;
+
+ return 0;
+}
+
+static int
+atmel_smc_nand_setup_data_interface(struct atmel_nand *nand,
+ int csline,
+ const struct nand_data_interface *conf)
+{
+ struct atmel_nand_controller *nc;
+ struct atmel_smc_cs_conf smcconf;
+ struct atmel_nand_cs *cs;
+ int ret;
+
+ nc = to_nand_controller(nand->controller);
+
+ ret = atmel_smc_nand_prepare_smcconf(nand, conf, &smcconf);
+ if (ret)
+ return ret;
+
+ if (csline == NAND_DATA_IFACE_CHECK_ONLY)
+ return 0;
+
+ cs = &nand->cs[csline];
+ cs->smcconf = smcconf;
+
+ atmel_smc_cs_conf_apply(nc->smc, cs->id, &cs->smcconf);
+
+ return 0;
+}
+
+static int
+atmel_hsmc_nand_setup_data_interface(struct atmel_nand *nand,
+ int csline,
+ const struct nand_data_interface *conf)
+{
+ struct atmel_hsmc_nand_controller *nc;
+ struct atmel_smc_cs_conf smcconf;
+ struct atmel_nand_cs *cs;
+ int ret;
+
+ nc = to_hsmc_nand_controller(nand->controller);
+
+ ret = atmel_smc_nand_prepare_smcconf(nand, conf, &smcconf);
+ if (ret)
+ return ret;
+
+ if (csline == NAND_DATA_IFACE_CHECK_ONLY)
+ return 0;
+
+ cs = &nand->cs[csline];
+ cs->smcconf = smcconf;
+
+ if (cs->rb.type == ATMEL_NAND_NATIVE_RB)
+ cs->smcconf.timings |= ATMEL_HSMC_TIMINGS_RBNSEL(cs->rb.id);
+
+ atmel_hsmc_cs_conf_apply(nc->base.smc, nc->hsmc_layout, cs->id,
+ &cs->smcconf);
+
+ return 0;
+}
+
+static int atmel_nand_setup_data_interface(struct mtd_info *mtd, int csline,
+ const struct nand_data_interface *conf)
+{
+ struct nand_chip *chip = mtd_to_nand(mtd);
+ struct atmel_nand *nand = to_atmel_nand(chip);
+ struct atmel_nand_controller *nc;
+
+ nc = to_nand_controller(nand->controller);
+
+ if (csline >= nand->numcs ||
+ (csline < 0 && csline != NAND_DATA_IFACE_CHECK_ONLY))
+ return -EINVAL;
+
+ return nc->caps->ops->setup_data_interface(nand, csline, conf);
+}
+
+#define NAND_KEEP_TIMINGS 0x00800000
+
+static void atmel_nand_init(struct atmel_nand_controller *nc,
+ struct atmel_nand *nand)
+{
+ struct nand_chip *chip = &nand->base;
+ struct mtd_info *mtd = nand_to_mtd(chip);
+
+ mtd->dev->parent = nc->dev;
+ nand->controller = &nc->base;
+ nand->controller = &nc->base;
+
+ chip->cmd_ctrl = atmel_nand_cmd_ctrl;
+ chip->read_byte = atmel_nand_read_byte;
+ chip->write_byte = atmel_nand_write_byte;
+ chip->read_buf = atmel_nand_read_buf;
+ chip->write_buf = atmel_nand_write_buf;
+ chip->select_chip = atmel_nand_select_chip;
+ chip->setup_data_interface = atmel_nand_setup_data_interface;
+
+ if (!nc->mck || !nc->caps->ops->setup_data_interface)
+ chip->options |= NAND_KEEP_TIMINGS;
+
+ /* Some NANDs require a longer delay than the default one (20us). */
+ chip->chip_delay = 40;
+
+ /* Default to HW ECC if pmecc is available. */
+ if (nc->pmecc)
+ chip->ecc.mode = NAND_ECC_HW;
+}
+
+static void atmel_smc_nand_init(struct atmel_nand_controller *nc,
+ struct atmel_nand *nand)
+{
+ struct atmel_smc_nand_controller *smc_nc;
+ int i;
+
+ atmel_nand_init(nc, nand);
+
+ smc_nc = to_smc_nand_controller(nand->controller);
+ if (!smc_nc->ebi_csa_regmap)
+ return;
+
+ /* Attach the CS to the NAND Flash logic. */
+ for (i = 0; i < nand->numcs; i++)
+ regmap_update_bits(smc_nc->ebi_csa_regmap,
+ smc_nc->ebi_csa->offs,
+ BIT(nand->cs[i].id), BIT(nand->cs[i].id));
+
+ if (smc_nc->ebi_csa->nfd0_on_d16)
+ regmap_update_bits(smc_nc->ebi_csa_regmap,
+ smc_nc->ebi_csa->offs,
+ smc_nc->ebi_csa->nfd0_on_d16,
+ smc_nc->ebi_csa->nfd0_on_d16);
+}
+
+static void atmel_hsmc_nand_init(struct atmel_nand_controller *nc,
+ struct atmel_nand *nand)
+{
+ struct nand_chip *chip = &nand->base;
+
+ atmel_nand_init(nc, nand);
+
+ /* Overload some methods for the HSMC controller. */
+ chip->cmd_ctrl = atmel_hsmc_nand_cmd_ctrl;
+ chip->select_chip = atmel_hsmc_nand_select_chip;
+}
+
+static int atmel_nand_controller_remove_nand(struct atmel_nand *nand)
+{
+ list_del(&nand->node);
+
+ return 0;
+}
+
+static struct atmel_nand *atmel_nand_create(struct atmel_nand_controller *nc,
+ ofnode np,
+ int reg_cells)
+{
+ struct atmel_nand *nand;
+ ofnode n;
+ int numcs = 0;
+ int ret, i;
+ u32 val;
+ fdt32_t faddr;
+ phys_addr_t base;
+
+ /* Count num of nand nodes */
+ ofnode_for_each_subnode(n, ofnode_get_parent(np))
+ numcs++;
+ if (numcs < 1) {
+ dev_err(nc->dev, "Missing or invalid reg property\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ nand = devm_kzalloc(nc->dev,
+ sizeof(struct atmel_nand) +
+ (numcs * sizeof(struct atmel_nand_cs)),
+ GFP_KERNEL);
+ if (!nand) {
+ dev_err(nc->dev, "Failed to allocate NAND object\n");
+ return ERR_PTR(-ENOMEM);
+ }
+
+ nand->numcs = numcs;
+
+ gpio_request_by_name_nodev(np, "det-gpios", 0, &nand->cdgpio,
+ GPIOD_IS_IN);
+
+ for (i = 0; i < numcs; i++) {
+ ret = ofnode_read_u32(np, "reg", &val);
+ if (ret) {
+ dev_err(nc->dev, "Invalid reg property (err = %d)\n",
+ ret);
+ return ERR_PTR(ret);
+ }
+ nand->cs[i].id = val;
+
+ /* Read base address */
+ struct resource res;
+
+ if (ofnode_read_resource(np, 0, &res)) {
+ dev_err(nc->dev, "Unable to read resource\n");
+ return ERR_PTR(-ENOMEM);
+ }
+
+ faddr = cpu_to_fdt32(val);
+ base = ofnode_translate_address(np, &faddr);
+ nand->cs[i].io.virt = (void *)base;
+
+ if (!ofnode_read_u32(np, "atmel,rb", &val)) {
+ if (val > ATMEL_NFC_MAX_RB_ID)
+ return ERR_PTR(-EINVAL);
+
+ nand->cs[i].rb.type = ATMEL_NAND_NATIVE_RB;
+ nand->cs[i].rb.id = val;
+ } else {
+ gpio_request_by_name_nodev(np, "rb-gpios", 0,
+ &nand->cs[i].rb.gpio,
+ GPIOD_IS_IN);
+ nand->cs[i].rb.type = ATMEL_NAND_GPIO_RB;
+ }
+
+ gpio_request_by_name_nodev(np, "cs-gpios", 0,
+ &nand->cs[i].csgpio,
+ GPIOD_IS_OUT);
+ }
+
+ nand_set_flash_node(&nand->base, np);
+
+ return nand;
+}
+
+static int nand_attach(struct nand_chip *chip)
+{
+ struct atmel_nand *nand = to_atmel_nand(chip);
+
+ if (nand->controller->ops && nand->controller->ops->attach_chip)
+ return nand->controller->ops->attach_chip(chip);
+
+ return 0;
+}
+
+int atmel_nand_scan(struct mtd_info *mtd, int maxchips)
+{
+ int ret;
+
+ ret = nand_scan_ident(mtd, maxchips, NULL);
+ if (ret)
+ return ret;
+
+ ret = nand_attach(mtd_to_nand(mtd));
+ if (ret)
+ return ret;
+
+ ret = nand_scan_tail(mtd);
+ return ret;
+}
+
+static int
+atmel_nand_controller_add_nand(struct atmel_nand_controller *nc,
+ struct atmel_nand *nand)
+{
+ struct nand_chip *chip = &nand->base;
+ struct mtd_info *mtd = nand_to_mtd(chip);
+ int ret;
+
+ /* No card inserted, skip this NAND. */
+ if (dm_gpio_is_valid(&nand->cdgpio) &&
+ dm_gpio_get_value(&nand->cdgpio)) {
+ dev_info(nc->dev, "No SmartMedia card inserted.\n");
+ return 0;
+ }
+
+ nc->caps->ops->nand_init(nc, nand);
+
+ ret = atmel_nand_scan(mtd, nand->numcs);
+ if (ret) {
+ dev_err(nc->dev, "NAND scan failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = nand_register(0, mtd);
+ if (ret) {
+ dev_err(nc->dev, "nand register failed: %d\n", ret);
+ return ret;
+ }
+
+ list_add_tail(&nand->node, &nc->chips);
+
+ return 0;
+}
+
+static int
+atmel_nand_controller_remove_nands(struct atmel_nand_controller *nc)
+{
+ struct atmel_nand *nand, *tmp;
+ int ret;
+
+ list_for_each_entry_safe(nand, tmp, &nc->chips, node) {
+ ret = atmel_nand_controller_remove_nand(nand);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int atmel_nand_controller_add_nands(struct atmel_nand_controller *nc)
+{
+ ofnode np;
+ ofnode nand_np;
+ int ret, reg_cells;
+ u32 val;
+
+ /* TODO:
+ * Add support for legacy nands
+ */
+
+ np = nc->dev->node_;
+
+ ret = ofnode_read_u32(np, "#address-cells", &val);
+ if (ret) {
+ dev_err(nc->dev, "missing #address-cells property\n");
+ return ret;
+ }
+
+ reg_cells = val;
+
+ ret = ofnode_read_u32(np, "#size-cells", &val);
+ if (ret) {
+ dev_err(nc->dev, "missing #size-cells property\n");
+ return ret;
+ }
+
+ reg_cells += val;
+
+ ofnode_for_each_subnode(nand_np, np) {
+ struct atmel_nand *nand;
+
+ nand = atmel_nand_create(nc, nand_np, reg_cells);
+ if (IS_ERR(nand)) {
+ ret = PTR_ERR(nand);
+ goto err;
+ }
+
+ ret = atmel_nand_controller_add_nand(nc, nand);
+ if (ret)
+ goto err;
+ }
+
+ return 0;
+
+err:
+ atmel_nand_controller_remove_nands(nc);
+
+ return ret;
+}
+
+static const struct atmel_smc_nand_ebi_csa_cfg at91sam9260_ebi_csa = {
+ .offs = AT91SAM9260_MATRIX_EBICSA,
+};
+
+static const struct atmel_smc_nand_ebi_csa_cfg at91sam9261_ebi_csa = {
+ .offs = AT91SAM9261_MATRIX_EBICSA,
+};
+
+static const struct atmel_smc_nand_ebi_csa_cfg at91sam9263_ebi_csa = {
+ .offs = AT91SAM9263_MATRIX_EBI0CSA,
+};
+
+static const struct atmel_smc_nand_ebi_csa_cfg at91sam9rl_ebi_csa = {
+ .offs = AT91SAM9RL_MATRIX_EBICSA,
+};
+
+static const struct atmel_smc_nand_ebi_csa_cfg at91sam9g45_ebi_csa = {
+ .offs = AT91SAM9G45_MATRIX_EBICSA,
+};
+
+static const struct atmel_smc_nand_ebi_csa_cfg at91sam9n12_ebi_csa = {
+ .offs = AT91SAM9N12_MATRIX_EBICSA,
+};
+
+static const struct atmel_smc_nand_ebi_csa_cfg at91sam9x5_ebi_csa = {
+ .offs = AT91SAM9X5_MATRIX_EBICSA,
+};
+
+static const struct atmel_smc_nand_ebi_csa_cfg sam9x60_ebi_csa = {
+ .offs = AT91_SFR_CCFG_EBICSA,
+ .nfd0_on_d16 = AT91_SFR_CCFG_NFD0_ON_D16,
+};
+
+static const struct udevice_id atmel_ebi_csa_regmap_of_ids[] = {
+ {
+ .compatible = "atmel,at91sam9260-matrix",
+ .data = (ulong)&at91sam9260_ebi_csa,
+ },
+ {
+ .compatible = "atmel,at91sam9261-matrix",
+ .data = (ulong)&at91sam9261_ebi_csa,
+ },
+ {
+ .compatible = "atmel,at91sam9263-matrix",
+ .data = (ulong)&at91sam9263_ebi_csa,
+ },
+ {
+ .compatible = "atmel,at91sam9rl-matrix",
+ .data = (ulong)&at91sam9rl_ebi_csa,
+ },
+ {
+ .compatible = "atmel,at91sam9g45-matrix",
+ .data = (ulong)&at91sam9g45_ebi_csa,
+ },
+ {
+ .compatible = "atmel,at91sam9n12-matrix",
+ .data = (ulong)&at91sam9n12_ebi_csa,
+ },
+ {
+ .compatible = "atmel,at91sam9x5-matrix",
+ .data = (ulong)&at91sam9x5_ebi_csa,
+ },
+ {
+ .compatible = "microchip,sam9x60-sfr",
+ .data = (ulong)&sam9x60_ebi_csa,
+ },
+ { /* sentinel */ },
+};
+
+static int atmel_nand_attach_chip(struct nand_chip *chip)
+{
+ struct atmel_nand *nand = to_atmel_nand(chip);
+ struct atmel_nand_controller *nc = to_nand_controller(nand->controller);
+ struct mtd_info *mtd = nand_to_mtd(chip);
+ int ret;
+
+ ret = nc->caps->ops->ecc_init(chip);
+ if (ret)
+ return ret;
+
+ if (nc->caps->legacy_of_bindings || !ofnode_valid(nc->dev->node_)) {
+ /*
+ * We keep the MTD name unchanged to avoid breaking platforms
+ * where the MTD cmdline parser is used and the bootloader
+ * has not been updated to use the new naming scheme.
+ */
+ mtd->name = "atmel_nand";
+ } else if (!mtd->name) {
+ /*
+ * If the new bindings are used and the bootloader has not been
+ * updated to pass a new mtdparts parameter on the cmdline, you
+ * should define the following property in your nand node:
+ *
+ * label = "atmel_nand";
+ *
+ * This way, mtd->name will be set by the core when
+ * nand_set_flash_node() is called.
+ */
+ sprintf(mtd->name, "%s:nand.%d", nc->dev->name, nand->cs[0].id);
+ }
+
+ return 0;
+}
+
+static const struct nand_controller_ops atmel_nand_controller_ops = {
+ .attach_chip = atmel_nand_attach_chip,
+};
+
+static int
+atmel_nand_controller_init(struct atmel_nand_controller *nc,
+ struct udevice *dev,
+ const struct atmel_nand_controller_caps *caps)
+{
+ struct ofnode_phandle_args args;
+ int ret;
+
+ nc->base.ops = &atmel_nand_controller_ops;
+ INIT_LIST_HEAD(&nc->chips);
+ nc->dev = dev;
+ nc->caps = caps;
+
+ nc->pmecc = devm_atmel_pmecc_get(dev);
+ if (IS_ERR(nc->pmecc)) {
+ ret = PTR_ERR(nc->pmecc);
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "Could not get PMECC object (err = %d)\n",
+ ret);
+ return ret;
+ }
+
+ /* We do not retrieve the SMC syscon when parsing old DTs. */
+ if (nc->caps->legacy_of_bindings)
+ return 0;
+
+ nc->mck = devm_kzalloc(dev, sizeof(nc->mck), GFP_KERNEL);
+ if (!nc->mck)
+ return -ENOMEM;
+
+ clk_get_by_index(dev->parent, 0, nc->mck);
+ if (IS_ERR(nc->mck)) {
+ dev_err(dev, "Failed to retrieve MCK clk\n");
+ return PTR_ERR(nc->mck);
+ }
+
+ ret = ofnode_parse_phandle_with_args(dev->parent->node_,
+ "atmel,smc", NULL, 0, 0, &args);
+ if (ret) {
+ dev_err(dev, "Missing or invalid atmel,smc property\n");
+ return -EINVAL;
+ }
+
+ nc->smc = syscon_node_to_regmap(args.node);
+ if (IS_ERR(nc->smc)) {
+ ret = PTR_ERR(nc->smc);
+ dev_err(dev, "Could not get SMC regmap (err = %d)\n", ret);
+ return 0;
+ }
+
+ return 0;
+}
+
+static int
+atmel_smc_nand_controller_init(struct atmel_smc_nand_controller *nc)
+{
+ struct udevice *dev = nc->base.dev;
+ struct ofnode_phandle_args args;
+ const struct udevice_id *match = NULL;
+ const char *name;
+ int ret;
+ int len;
+ int i;
+
+ /* We do not retrieve the EBICSA regmap when parsing old DTs. */
+ if (nc->base.caps->legacy_of_bindings)
+ return 0;
+
+ ret = ofnode_parse_phandle_with_args(dev->parent->node_,
+ nc->base.caps->ebi_csa_regmap_name,
+ NULL, 0, 0, &args);
+ if (ret) {
+ dev_err(dev, "Unable to read ebi csa regmap\n");
+ return -EINVAL;
+ }
+
+ name = ofnode_get_property(args.node, "compatible", &len);
+
+ for (i = 0; i < ARRAY_SIZE(atmel_ebi_csa_regmap_of_ids); i++) {
+ if (!strcmp(name, atmel_ebi_csa_regmap_of_ids[i].compatible)) {
+ match = &atmel_ebi_csa_regmap_of_ids[i];
+ break;
+ }
+ }
+
+ if (!match) {
+ dev_err(dev, "Unable to find ebi csa conf");
+ return -EINVAL;
+ }
+ nc->ebi_csa = (struct atmel_smc_nand_ebi_csa_cfg *)match->data;
+
+ nc->ebi_csa_regmap = syscon_node_to_regmap(args.node);
+ if (IS_ERR(nc->ebi_csa_regmap)) {
+ ret = PTR_ERR(nc->ebi_csa_regmap);
+ dev_err(dev, "Could not get EBICSA regmap (err = %d)\n", ret);
+ return ret;
+ }
+
+ /* TODO:
+ * The at91sam9263 has 2 EBIs, if the NAND controller is under EBI1
+ * add 4 to ->ebi_csa->offs.
+ */
+
+ return 0;
+}
+
+static int atmel_hsmc_nand_controller_init(struct atmel_hsmc_nand_controller *nc)
+{
+ struct udevice *dev = nc->base.dev;
+ struct ofnode_phandle_args args;
+ struct clk smc_clk;
+ int ret;
+ u32 addr;
+
+ ret = ofnode_parse_phandle_with_args(dev->parent->node_,
+ "atmel,smc", NULL, 0, 0, &args);
+ if (ret) {
+ dev_err(dev, "Missing or invalid atmel,smc property\n");
+ return -EINVAL;
+ }
+
+ nc->hsmc_layout = atmel_hsmc_get_reg_layout(args.node);
+ if (IS_ERR(nc->hsmc_layout)) {
+ dev_err(dev, "Could not get hsmc layout\n");
+ return -EINVAL;
+ }
+
+ /* Enable smc clock */
+ ret = clk_get_by_index_nodev(args.node, 0, &smc_clk);
+ if (ret) {
+ dev_err(dev, "Unable to get smc clock (err = %d)", ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(&smc_clk);
+ if (ret)
+ return ret;
+
+ ret = ofnode_parse_phandle_with_args(dev->node_,
+ "atmel,nfc-io", NULL, 0, 0, &args);
+ if (ret) {
+ dev_err(dev, "Missing or invalid atmel,nfc-io property\n");
+ return -EINVAL;
+ }
+
+ nc->io = syscon_node_to_regmap(args.node);
+ if (IS_ERR(nc->io)) {
+ ret = PTR_ERR(nc->io);
+ dev_err(dev, "Could not get NFC IO regmap\n");
+ return ret;
+ }
+
+ ret = ofnode_parse_phandle_with_args(dev->node_,
+ "atmel,nfc-sram", NULL, 0, 0, &args);
+ if (ret) {
+ dev_err(dev, "Missing or invalid atmel,nfc-sram property\n");
+ return ret;
+ }
+
+ ret = ofnode_read_u32(args.node, "reg", &addr);
+ if (ret) {
+ dev_err(dev, "Could not read reg addr of nfc sram");
+ return ret;
+ }
+ nc->sram.virt = (void *)addr;
+
+ return 0;
+}
+
+static int
+atmel_hsmc_nand_controller_remove(struct atmel_nand_controller *nc)
+{
+ struct atmel_hsmc_nand_controller *hsmc_nc;
+ int ret;
+
+ ret = atmel_nand_controller_remove_nands(nc);
+ if (ret)
+ return ret;
+
+ hsmc_nc = container_of(nc, struct atmel_hsmc_nand_controller, base);
+
+ if (hsmc_nc->clk) {
+ clk_disable_unprepare(hsmc_nc->clk);
+ devm_clk_put(nc->dev, hsmc_nc->clk);
+ }
+
+ return 0;
+}
+
+static int
+atmel_hsmc_nand_controller_probe(struct udevice *dev,
+ const struct atmel_nand_controller_caps *caps)
+{
+ struct atmel_hsmc_nand_controller *nc;
+ int ret;
+
+ nc = devm_kzalloc(dev, sizeof(*nc), GFP_KERNEL);
+ if (!nc)
+ return -ENOMEM;
+
+ ret = atmel_nand_controller_init(&nc->base, dev, caps);
+ if (ret)
+ return ret;
+
+ ret = atmel_hsmc_nand_controller_init(nc);
+ if (ret)
+ return ret;
+
+ /* Make sure all irqs are masked before registering our IRQ handler. */
+ regmap_write(nc->base.smc, ATMEL_HSMC_NFC_IDR, 0xffffffff);
+
+ /* Initial NFC configuration. */
+ regmap_write(nc->base.smc, ATMEL_HSMC_NFC_CFG,
+ ATMEL_HSMC_NFC_CFG_DTO_MAX);
+
+ ret = atmel_nand_controller_add_nands(&nc->base);
+ if (ret)
+ goto err;
+
+ return 0;
+
+err:
+ atmel_hsmc_nand_controller_remove(&nc->base);
+
+ return ret;
+}
+
+static const struct atmel_nand_controller_ops atmel_hsmc_nc_ops = {
+ .probe = atmel_hsmc_nand_controller_probe,
+ .remove = atmel_hsmc_nand_controller_remove,
+ .ecc_init = atmel_hsmc_nand_ecc_init,
+ .nand_init = atmel_hsmc_nand_init,
+ .setup_data_interface = atmel_hsmc_nand_setup_data_interface,
+};
+
+static const struct atmel_nand_controller_caps atmel_sama5_nc_caps = {
+ .has_dma = true,
+ .ale_offs = BIT(21),
+ .cle_offs = BIT(22),
+ .ops = &atmel_hsmc_nc_ops,
+};
+
+static int
+atmel_smc_nand_controller_probe(struct udevice *dev,
+ const struct atmel_nand_controller_caps *caps)
+{
+ struct atmel_smc_nand_controller *nc;
+ int ret;
+
+ nc = devm_kzalloc(dev, sizeof(*nc), GFP_KERNEL);
+ if (!nc)
+ return -ENOMEM;
+
+ ret = atmel_nand_controller_init(&nc->base, dev, caps);
+ if (ret)
+ return ret;
+
+ ret = atmel_smc_nand_controller_init(nc);
+ if (ret)
+ return ret;
+
+ return atmel_nand_controller_add_nands(&nc->base);
+}
+
+static int
+atmel_smc_nand_controller_remove(struct atmel_nand_controller *nc)
+{
+ int ret;
+
+ ret = atmel_nand_controller_remove_nands(nc);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+/*
+ * The SMC reg layout of at91rm9200 is completely different which prevents us
+ * from re-using atmel_smc_nand_setup_data_interface() for the
+ * ->setup_data_interface() hook.
+ * At this point, there's no support for the at91rm9200 SMC IP, so we leave
+ * ->setup_data_interface() unassigned.
+ */
+static const struct atmel_nand_controller_ops at91rm9200_nc_ops = {
+ .probe = atmel_smc_nand_controller_probe,
+ .remove = atmel_smc_nand_controller_remove,
+ .ecc_init = atmel_nand_ecc_init,
+ .nand_init = atmel_smc_nand_init,
+};
+
+static const struct atmel_nand_controller_caps atmel_rm9200_nc_caps = {
+ .ale_offs = BIT(21),
+ .cle_offs = BIT(22),
+ .ebi_csa_regmap_name = "atmel,matrix",
+ .ops = &at91rm9200_nc_ops,
+};
+
+static const struct atmel_nand_controller_ops atmel_smc_nc_ops = {
+ .probe = atmel_smc_nand_controller_probe,
+ .remove = atmel_smc_nand_controller_remove,
+ .ecc_init = atmel_nand_ecc_init,
+ .nand_init = atmel_smc_nand_init,
+ .setup_data_interface = atmel_smc_nand_setup_data_interface,
+};
+
+static const struct atmel_nand_controller_caps atmel_sam9260_nc_caps = {
+ .ale_offs = BIT(21),
+ .cle_offs = BIT(22),
+ .ebi_csa_regmap_name = "atmel,matrix",
+ .ops = &atmel_smc_nc_ops,
+};
+
+static const struct atmel_nand_controller_caps atmel_sam9261_nc_caps = {
+ .ale_offs = BIT(22),
+ .cle_offs = BIT(21),
+ .ebi_csa_regmap_name = "atmel,matrix",
+ .ops = &atmel_smc_nc_ops,
+};
+
+static const struct atmel_nand_controller_caps atmel_sam9g45_nc_caps = {
+ .has_dma = true,
+ .ale_offs = BIT(21),
+ .cle_offs = BIT(22),
+ .ebi_csa_regmap_name = "atmel,matrix",
+ .ops = &atmel_smc_nc_ops,
+};
+
+static const struct atmel_nand_controller_caps microchip_sam9x60_nc_caps = {
+ .has_dma = true,
+ .ale_offs = BIT(21),
+ .cle_offs = BIT(22),
+ .ebi_csa_regmap_name = "microchip,sfr",
+ .ops = &atmel_smc_nc_ops,
+};
+
+/* Only used to parse old bindings. */
+static const struct atmel_nand_controller_caps atmel_rm9200_nand_caps = {
+ .ale_offs = BIT(21),
+ .cle_offs = BIT(22),
+ .ops = &atmel_smc_nc_ops,
+ .legacy_of_bindings = true,
+};
+
+static const struct udevice_id atmel_nand_controller_of_ids[] = {
+ {
+ .compatible = "atmel,at91rm9200-nand-controller",
+ .data = (ulong)&atmel_rm9200_nc_caps,
+ },
+ {
+ .compatible = "atmel,at91sam9260-nand-controller",
+ .data = (ulong)&atmel_sam9260_nc_caps,
+ },
+ {
+ .compatible = "atmel,at91sam9261-nand-controller",
+ .data = (ulong)&atmel_sam9261_nc_caps,
+ },
+ {
+ .compatible = "atmel,at91sam9g45-nand-controller",
+ .data = (ulong)&atmel_sam9g45_nc_caps,
+ },
+ {
+ .compatible = "atmel,sama5d3-nand-controller",
+ .data = (ulong)&atmel_sama5_nc_caps,
+ },
+ {
+ .compatible = "microchip,sam9x60-nand-controller",
+ .data = (ulong)&microchip_sam9x60_nc_caps,
+ },
+ /* Support for old/deprecated bindings: */
+ {
+ .compatible = "atmel,at91rm9200-nand",
+ .data = (ulong)&atmel_rm9200_nand_caps,
+ },
+ {
+ .compatible = "atmel,sama5d4-nand",
+ .data = (ulong)&atmel_rm9200_nand_caps,
+ },
+ {
+ .compatible = "atmel,sama5d2-nand",
+ .data = (ulong)&atmel_rm9200_nand_caps,
+ },
+ { /* sentinel */ },
+};
+
+static int atmel_nand_controller_probe(struct udevice *dev)
+{
+ const struct atmel_nand_controller_caps *caps;
+ struct udevice *pmecc_dev;
+
+ caps = (struct atmel_nand_controller_caps *)dev_get_driver_data(dev);
+ if (!caps) {
+ printf("Could not retrieve NFC caps\n");
+ return -EINVAL;
+ }
+
+ /* Probe pmecc driver */
+ if (uclass_get_device(UCLASS_MTD, 1, &pmecc_dev)) {
+ printf("%s: get device fail\n", __func__);
+ return -EINVAL;
+ }
+
+ return caps->ops->probe(dev, caps);
+}
+
+static int atmel_nand_controller_remove(struct udevice *dev)
+{
+ struct atmel_nand_controller *nc;
+
+ nc = (struct atmel_nand_controller *)dev_get_driver_data(dev);
+
+ return nc->caps->ops->remove(nc);
+}
+
+U_BOOT_DRIVER(atmel_nand_controller) = {
+ .name = "atmel-nand-controller",
+ .id = UCLASS_MTD,
+ .of_match = atmel_nand_controller_of_ids,
+ .probe = atmel_nand_controller_probe,
+ .remove = atmel_nand_controller_remove,
+};
+
+void board_nand_init(void)
+{
+ struct udevice *dev;
+ int ret;
+
+ ret = uclass_get_device_by_driver(UCLASS_MTD,
+ DM_DRIVER_GET(atmel_nand_controller),
+ &dev);
+ if (ret && ret != -ENODEV)
+ printf("Failed to initialize NAND controller. (error %d)\n",
+ ret);
+}
diff --git a/drivers/mtd/nand/raw/atmel/pmecc.c b/drivers/mtd/nand/raw/atmel/pmecc.c
new file mode 100644
index 00000000000..e2e3f1ee6b5
--- /dev/null
+++ b/drivers/mtd/nand/raw/atmel/pmecc.c
@@ -0,0 +1,965 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2017 ATMEL
+ * Copyright 2017 Free Electrons
+ *
+ * Author: Boris Brezillon <boris.brezillon@free-electrons.com>
+ *
+ * Derived from the atmel_nand.c driver which contained the following
+ * copyrights:
+ *
+ * Copyright 2003 Rick Bronson
+ *
+ * Derived from drivers/mtd/nand/autcpu12.c (removed in v3.8)
+ * Copyright 2001 Thomas Gleixner (gleixner@autronix.de)
+ *
+ * Derived from drivers/mtd/spia.c (removed in v3.8)
+ * Copyright 2000 Steven J. Hill (sjhill@cotw.com)
+ *
+ * Add Hardware ECC support for AT91SAM9260 / AT91SAM9263
+ * Richard Genoud (richard.genoud@gmail.com), Adeneo Copyright 2007
+ *
+ * Derived from Das U-Boot source code
+ * (u-boot-1.1.5/board/atmel/at91sam9263ek/nand.c)
+ * Copyright 2006 ATMEL Rousset, Lacressonniere Nicolas
+ *
+ * Add Programmable Multibit ECC support for various AT91 SoC
+ * Copyright 2012 ATMEL, Hong Xu
+ *
+ * Add Nand Flash Controller support for SAMA5 SoC
+ * Copyright 2013 ATMEL, Josh Wu (josh.wu@atmel.com)
+ *
+ * The PMECC is an hardware assisted BCH engine, which means part of the
+ * ECC algorithm is left to the software. The hardware/software repartition
+ * is explained in the "PMECC Controller Functional Description" chapter in
+ * Atmel datasheets, and some of the functions in this file are directly
+ * implementing the algorithms described in the "Software Implementation"
+ * sub-section.
+ *
+ * TODO: it seems that the software BCH implementation in lib/bch.c is already
+ * providing some of the logic we are implementing here. It would be smart
+ * to expose the needed lib/bch.c helpers/functions and re-use them here.
+ */
+#include <linux/iopoll.h>
+#include <linux/mtd/rawnand.h>
+#include <dm/of_access.h>
+#include <dm/ofnode.h>
+#include <asm/io.h>
+#include "pmecc.h"
+#include <dm/uclass.h>
+#include <dm/device_compat.h>
+#include <dm/devres.h>
+#include <linux/ioport.h>
+
+/* Galois field dimension */
+#define PMECC_GF_DIMENSION_13 13
+#define PMECC_GF_DIMENSION_14 14
+
+/* Primitive Polynomial used by PMECC */
+#define PMECC_GF_13_PRIMITIVE_POLY 0x201b
+#define PMECC_GF_14_PRIMITIVE_POLY 0x4443
+
+#define PMECC_LOOKUP_TABLE_SIZE_512 0x2000
+#define PMECC_LOOKUP_TABLE_SIZE_1024 0x4000
+
+/* Time out value for reading PMECC status register */
+#define PMECC_MAX_TIMEOUT_MS 100
+
+/* PMECC Register Definitions */
+#define ATMEL_PMECC_CFG 0x0
+#define PMECC_CFG_BCH_STRENGTH(x) (x)
+#define PMECC_CFG_BCH_STRENGTH_MASK GENMASK(2, 0)
+#define PMECC_CFG_SECTOR512 (0 << 4)
+#define PMECC_CFG_SECTOR1024 BIT(4)
+#define PMECC_CFG_NSECTORS(x) ((fls(x) - 1) << 8)
+#define PMECC_CFG_READ_OP (0 << 12)
+#define PMECC_CFG_WRITE_OP BIT(12)
+#define PMECC_CFG_SPARE_ENABLE BIT(16)
+#define PMECC_CFG_AUTO_ENABLE BIT(20)
+
+#define ATMEL_PMECC_SAREA 0x4
+#define ATMEL_PMECC_SADDR 0x8
+#define ATMEL_PMECC_EADDR 0xc
+
+#define ATMEL_PMECC_CLK 0x10
+#define PMECC_CLK_133MHZ (2 << 0)
+
+#define ATMEL_PMECC_CTRL 0x14
+#define PMECC_CTRL_RST BIT(0)
+#define PMECC_CTRL_DATA BIT(1)
+#define PMECC_CTRL_USER BIT(2)
+#define PMECC_CTRL_ENABLE BIT(4)
+#define PMECC_CTRL_DISABLE BIT(5)
+
+#define ATMEL_PMECC_SR 0x18
+#define PMECC_SR_BUSY BIT(0)
+#define PMECC_SR_ENABLE BIT(4)
+
+#define ATMEL_PMECC_IER 0x1c
+#define ATMEL_PMECC_IDR 0x20
+#define ATMEL_PMECC_IMR 0x24
+#define ATMEL_PMECC_ISR 0x28
+#define PMECC_ERROR_INT BIT(0)
+
+#define ATMEL_PMECC_ECC(sector, n) \
+ ((((sector) + 1) * 0x40) + (n))
+
+#define ATMEL_PMECC_REM(sector, n) \
+ ((((sector) + 1) * 0x40) + ((n) * 4) + 0x200)
+
+/* PMERRLOC Register Definitions */
+#define ATMEL_PMERRLOC_ELCFG 0x0
+#define PMERRLOC_ELCFG_SECTOR_512 (0 << 0)
+#define PMERRLOC_ELCFG_SECTOR_1024 BIT(0)
+#define PMERRLOC_ELCFG_NUM_ERRORS(n) ((n) << 16)
+
+#define ATMEL_PMERRLOC_ELPRIM 0x4
+#define ATMEL_PMERRLOC_ELEN 0x8
+#define ATMEL_PMERRLOC_ELDIS 0xc
+#define PMERRLOC_DISABLE BIT(0)
+
+#define ATMEL_PMERRLOC_ELSR 0x10
+#define PMERRLOC_ELSR_BUSY BIT(0)
+
+#define ATMEL_PMERRLOC_ELIER 0x14
+#define ATMEL_PMERRLOC_ELIDR 0x18
+#define ATMEL_PMERRLOC_ELIMR 0x1c
+#define ATMEL_PMERRLOC_ELISR 0x20
+#define PMERRLOC_ERR_NUM_MASK GENMASK(12, 8)
+#define PMERRLOC_CALC_DONE BIT(0)
+
+#define ATMEL_PMERRLOC_SIGMA(x) (((x) * 0x4) + 0x28)
+
+#define ATMEL_PMERRLOC_EL(offs, x) (((x) * 0x4) + (offs))
+
+struct atmel_pmecc_gf_tables {
+ u16 *alpha_to;
+ u16 *index_of;
+};
+
+struct atmel_pmecc_caps {
+ const int *strengths;
+ int nstrengths;
+ int el_offset;
+ bool correct_erased_chunks;
+};
+
+struct atmel_pmecc_user_conf_cache {
+ u32 cfg;
+ u32 sarea;
+ u32 saddr;
+ u32 eaddr;
+};
+
+struct atmel_pmecc_user {
+ struct atmel_pmecc_user_conf_cache cache;
+ struct atmel_pmecc *pmecc;
+ const struct atmel_pmecc_gf_tables *gf_tables;
+ int eccbytes;
+ s16 *partial_syn;
+ s16 *si;
+ s16 *lmu;
+ s16 *smu;
+ s32 *mu;
+ s32 *dmu;
+ s32 *delta;
+ u32 isr;
+};
+
+/* Serialize table access */
+DEFINE_MUTEX(pmecc_gf_tables_lock);
+static const struct atmel_pmecc_gf_tables *pmecc_gf_tables_512;
+static const struct atmel_pmecc_gf_tables *pmecc_gf_tables_1024;
+
+static inline int deg(unsigned int poly)
+{
+ /* polynomial degree is the most-significant bit index */
+ return fls(poly) - 1;
+}
+
+static int atmel_pmecc_build_gf_tables(int mm, unsigned int poly,
+ struct atmel_pmecc_gf_tables *gf_tables)
+{
+ unsigned int i, x = 1;
+ const unsigned int k = BIT(deg(poly));
+ unsigned int nn = BIT(mm) - 1;
+
+ /* primitive polynomial must be of degree m */
+ if (k != (1u << mm))
+ return -EINVAL;
+
+ for (i = 0; i < nn; i++) {
+ gf_tables->alpha_to[i] = x;
+ gf_tables->index_of[x] = i;
+ if (i && (x == 1))
+ /* polynomial is not primitive (a^i=1 with 0<i<2^m-1) */
+ return -EINVAL;
+ x <<= 1;
+ if (x & k)
+ x ^= poly;
+ }
+ gf_tables->alpha_to[nn] = 1;
+ gf_tables->index_of[0] = 0;
+
+ return 0;
+}
+
+static const struct atmel_pmecc_gf_tables *
+atmel_pmecc_create_gf_tables(const struct atmel_pmecc_user_req *req)
+{
+ struct atmel_pmecc_gf_tables *gf_tables;
+ unsigned int poly, degree, table_size;
+ int ret;
+
+ if (req->ecc.sectorsize == 512) {
+ degree = PMECC_GF_DIMENSION_13;
+ poly = PMECC_GF_13_PRIMITIVE_POLY;
+ table_size = PMECC_LOOKUP_TABLE_SIZE_512;
+ } else {
+ degree = PMECC_GF_DIMENSION_14;
+ poly = PMECC_GF_14_PRIMITIVE_POLY;
+ table_size = PMECC_LOOKUP_TABLE_SIZE_1024;
+ }
+
+ gf_tables = kzalloc(sizeof(*gf_tables) +
+ (2 * table_size * sizeof(u16)),
+ GFP_KERNEL);
+ if (!gf_tables)
+ return ERR_PTR(-ENOMEM);
+
+ gf_tables->alpha_to = (void *)(gf_tables + 1);
+ gf_tables->index_of = gf_tables->alpha_to + table_size;
+
+ ret = atmel_pmecc_build_gf_tables(degree, poly, gf_tables);
+ if (ret) {
+ kfree(gf_tables);
+ return ERR_PTR(ret);
+ }
+
+ return gf_tables;
+}
+
+static const struct atmel_pmecc_gf_tables *
+atmel_pmecc_get_gf_tables(const struct atmel_pmecc_user_req *req)
+{
+ const struct atmel_pmecc_gf_tables **gf_tables, *ret;
+
+ mutex_lock(&pmecc_gf_tables_lock);
+ if (req->ecc.sectorsize == 512)
+ gf_tables = &pmecc_gf_tables_512;
+ else
+ gf_tables = &pmecc_gf_tables_1024;
+
+ ret = *gf_tables;
+
+ if (!ret) {
+ ret = atmel_pmecc_create_gf_tables(req);
+ if (!IS_ERR(ret))
+ *gf_tables = ret;
+ }
+ mutex_unlock(&pmecc_gf_tables_lock);
+
+ return ret;
+}
+
+static int atmel_pmecc_prepare_user_req(struct atmel_pmecc *pmecc,
+ struct atmel_pmecc_user_req *req)
+{
+ int i, max_eccbytes, eccbytes = 0, eccstrength = 0;
+
+ if (req->pagesize <= 0 || req->oobsize <= 0 || req->ecc.bytes <= 0)
+ return -EINVAL;
+
+ if (req->ecc.ooboffset >= 0 &&
+ req->ecc.ooboffset + req->ecc.bytes > req->oobsize)
+ return -EINVAL;
+
+ if (req->ecc.sectorsize == ATMEL_PMECC_SECTOR_SIZE_AUTO) {
+ if (req->ecc.strength != ATMEL_PMECC_MAXIMIZE_ECC_STRENGTH)
+ return -EINVAL;
+
+ if (req->pagesize > 512)
+ req->ecc.sectorsize = 1024;
+ else
+ req->ecc.sectorsize = 512;
+ }
+
+ if (req->ecc.sectorsize != 512 && req->ecc.sectorsize != 1024)
+ return -EINVAL;
+
+ if (req->pagesize % req->ecc.sectorsize)
+ return -EINVAL;
+
+ req->ecc.nsectors = req->pagesize / req->ecc.sectorsize;
+
+ max_eccbytes = req->ecc.bytes;
+
+ for (i = 0; i < pmecc->caps->nstrengths; i++) {
+ int nbytes, strength = pmecc->caps->strengths[i];
+
+ if (req->ecc.strength != ATMEL_PMECC_MAXIMIZE_ECC_STRENGTH &&
+ strength < req->ecc.strength)
+ continue;
+
+ nbytes = DIV_ROUND_UP(strength * fls(8 * req->ecc.sectorsize),
+ 8);
+ nbytes *= req->ecc.nsectors;
+
+ if (nbytes > max_eccbytes)
+ break;
+
+ eccstrength = strength;
+ eccbytes = nbytes;
+
+ if (req->ecc.strength != ATMEL_PMECC_MAXIMIZE_ECC_STRENGTH)
+ break;
+ }
+
+ if (!eccstrength)
+ return -EINVAL;
+
+ req->ecc.bytes = eccbytes;
+ req->ecc.strength = eccstrength;
+
+ if (req->ecc.ooboffset < 0)
+ req->ecc.ooboffset = req->oobsize - eccbytes;
+
+ return 0;
+}
+
+struct atmel_pmecc_user *
+atmel_pmecc_create_user(struct atmel_pmecc *pmecc,
+ struct atmel_pmecc_user_req *req)
+{
+ struct atmel_pmecc_user *user;
+ const struct atmel_pmecc_gf_tables *gf_tables;
+ int strength, size, ret;
+
+ ret = atmel_pmecc_prepare_user_req(pmecc, req);
+ if (ret)
+ return ERR_PTR(ret);
+
+ size = sizeof(*user);
+ size = ALIGN(size, sizeof(u16));
+ /* Reserve space for partial_syn, si and smu */
+ size += ((2 * req->ecc.strength) + 1) * sizeof(u16) *
+ (2 + req->ecc.strength + 2);
+ /* Reserve space for lmu. */
+ size += (req->ecc.strength + 1) * sizeof(u16);
+ /* Reserve space for mu, dmu and delta. */
+ size = ALIGN(size, sizeof(s32));
+ size += (req->ecc.strength + 1) * sizeof(s32) * 3;
+
+ user = kzalloc(size, GFP_KERNEL);
+ if (!user)
+ return ERR_PTR(-ENOMEM);
+
+ user->pmecc = pmecc;
+
+ user->partial_syn = (s16 *)PTR_ALIGN(user + 1, sizeof(u16));
+ user->si = user->partial_syn + ((2 * req->ecc.strength) + 1);
+ user->lmu = user->si + ((2 * req->ecc.strength) + 1);
+ user->smu = user->lmu + (req->ecc.strength + 1);
+ user->mu = (s32 *)PTR_ALIGN(user->smu +
+ (((2 * req->ecc.strength) + 1) *
+ (req->ecc.strength + 2)),
+ sizeof(s32));
+ user->dmu = user->mu + req->ecc.strength + 1;
+ user->delta = user->dmu + req->ecc.strength + 1;
+
+ gf_tables = atmel_pmecc_get_gf_tables(req);
+ if (IS_ERR(gf_tables)) {
+ kfree(user);
+ return ERR_CAST(gf_tables);
+ }
+
+ user->gf_tables = gf_tables;
+
+ user->eccbytes = req->ecc.bytes / req->ecc.nsectors;
+
+ for (strength = 0; strength < pmecc->caps->nstrengths; strength++) {
+ if (pmecc->caps->strengths[strength] == req->ecc.strength)
+ break;
+ }
+
+ user->cache.cfg = PMECC_CFG_BCH_STRENGTH(strength) |
+ PMECC_CFG_NSECTORS(req->ecc.nsectors);
+
+ if (req->ecc.sectorsize == 1024)
+ user->cache.cfg |= PMECC_CFG_SECTOR1024;
+
+ user->cache.sarea = req->oobsize - 1;
+ user->cache.saddr = req->ecc.ooboffset;
+ user->cache.eaddr = req->ecc.ooboffset + req->ecc.bytes - 1;
+
+ return user;
+}
+EXPORT_SYMBOL_GPL(atmel_pmecc_create_user);
+
+void atmel_pmecc_destroy_user(struct atmel_pmecc_user *user)
+{
+ kfree(user);
+}
+EXPORT_SYMBOL_GPL(atmel_pmecc_destroy_user);
+
+static int get_strength(struct atmel_pmecc_user *user)
+{
+ const int *strengths = user->pmecc->caps->strengths;
+
+ return strengths[user->cache.cfg & PMECC_CFG_BCH_STRENGTH_MASK];
+}
+
+static int get_sectorsize(struct atmel_pmecc_user *user)
+{
+ return user->cache.cfg & PMECC_CFG_SECTOR1024 ? 1024 : 512;
+}
+
+static void atmel_pmecc_gen_syndrome(struct atmel_pmecc_user *user, int sector)
+{
+ int strength = get_strength(user);
+ u32 value;
+ int i;
+
+ /* Fill odd syndromes */
+ for (i = 0; i < strength; i++) {
+ value = readl_relaxed(user->pmecc->regs.base +
+ ATMEL_PMECC_REM(sector, i / 2));
+ if (i & 1)
+ value >>= 16;
+
+ user->partial_syn[(2 * i) + 1] = value;
+ }
+}
+
+static void atmel_pmecc_substitute(struct atmel_pmecc_user *user)
+{
+ int degree = get_sectorsize(user) == 512 ? 13 : 14;
+ int cw_len = BIT(degree) - 1;
+ int strength = get_strength(user);
+ s16 *alpha_to = (s16 *)user->gf_tables->alpha_to;
+ s16 *index_of = (s16 *)user->gf_tables->index_of;
+ s16 *partial_syn = user->partial_syn;
+ s16 *si;
+ int i, j;
+
+ /*
+ * si[] is a table that holds the current syndrome value,
+ * an element of that table belongs to the field
+ */
+ si = user->si;
+
+ memset(&si[1], 0, sizeof(s16) * ((2 * strength) - 1));
+
+ /* Computation 2t syndromes based on S(x) */
+ /* Odd syndromes */
+ for (i = 1; i < 2 * strength; i += 2) {
+ for (j = 0; j < degree; j++) {
+ if (partial_syn[i] & BIT(j))
+ si[i] = alpha_to[i * j] ^ si[i];
+ }
+ }
+ /* Even syndrome = (Odd syndrome) ** 2 */
+ for (i = 2, j = 1; j <= strength; i = ++j << 1) {
+ if (si[j] == 0) {
+ si[i] = 0;
+ } else {
+ s16 tmp;
+
+ tmp = index_of[si[j]];
+ tmp = (tmp * 2) % cw_len;
+ si[i] = alpha_to[tmp];
+ }
+ }
+}
+
+static void atmel_pmecc_get_sigma(struct atmel_pmecc_user *user)
+{
+ s16 *lmu = user->lmu;
+ s16 *si = user->si;
+ s32 *mu = user->mu;
+ s32 *dmu = user->dmu;
+ s32 *delta = user->delta;
+ int degree = get_sectorsize(user) == 512 ? 13 : 14;
+ int cw_len = BIT(degree) - 1;
+ int strength = get_strength(user);
+ int num = 2 * strength + 1;
+ s16 *index_of = (s16 *)user->gf_tables->index_of;
+ s16 *alpha_to = (s16 *)user->gf_tables->alpha_to;
+ int i, j, k;
+ u32 dmu_0_count, tmp;
+ s16 *smu = user->smu;
+
+ /* index of largest delta */
+ int ro;
+ int largest;
+ int diff;
+
+ dmu_0_count = 0;
+
+ /* First Row */
+
+ /* Mu */
+ mu[0] = -1;
+
+ memset(smu, 0, sizeof(s16) * num);
+ smu[0] = 1;
+
+ /* discrepancy set to 1 */
+ dmu[0] = 1;
+ /* polynom order set to 0 */
+ lmu[0] = 0;
+ delta[0] = (mu[0] * 2 - lmu[0]) >> 1;
+
+ /* Second Row */
+
+ /* Mu */
+ mu[1] = 0;
+ /* Sigma(x) set to 1 */
+ memset(&smu[num], 0, sizeof(s16) * num);
+ smu[num] = 1;
+
+ /* discrepancy set to S1 */
+ dmu[1] = si[1];
+
+ /* polynom order set to 0 */
+ lmu[1] = 0;
+
+ delta[1] = (mu[1] * 2 - lmu[1]) >> 1;
+
+ /* Init the Sigma(x) last row */
+ memset(&smu[(strength + 1) * num], 0, sizeof(s16) * num);
+
+ for (i = 1; i <= strength; i++) {
+ mu[i + 1] = i << 1;
+ /* Begin Computing Sigma (Mu+1) and L(mu) */
+ /* check if discrepancy is set to 0 */
+ if (dmu[i] == 0) {
+ dmu_0_count++;
+
+ tmp = ((strength - (lmu[i] >> 1) - 1) / 2);
+ if ((strength - (lmu[i] >> 1) - 1) & 0x1)
+ tmp += 2;
+ else
+ tmp += 1;
+
+ if (dmu_0_count == tmp) {
+ for (j = 0; j <= (lmu[i] >> 1) + 1; j++)
+ smu[(strength + 1) * num + j] =
+ smu[i * num + j];
+
+ lmu[strength + 1] = lmu[i];
+ return;
+ }
+
+ /* copy polynom */
+ for (j = 0; j <= lmu[i] >> 1; j++)
+ smu[(i + 1) * num + j] = smu[i * num + j];
+
+ /* copy previous polynom order to the next */
+ lmu[i + 1] = lmu[i];
+ } else {
+ ro = 0;
+ largest = -1;
+ /* find largest delta with dmu != 0 */
+ for (j = 0; j < i; j++) {
+ if ((dmu[j]) && (delta[j] > largest)) {
+ largest = delta[j];
+ ro = j;
+ }
+ }
+
+ /* compute difference */
+ diff = (mu[i] - mu[ro]);
+
+ /* Compute degree of the new smu polynomial */
+ if ((lmu[i] >> 1) > ((lmu[ro] >> 1) + diff))
+ lmu[i + 1] = lmu[i];
+ else
+ lmu[i + 1] = ((lmu[ro] >> 1) + diff) * 2;
+
+ /* Init smu[i+1] with 0 */
+ for (k = 0; k < num; k++)
+ smu[(i + 1) * num + k] = 0;
+
+ /* Compute smu[i+1] */
+ for (k = 0; k <= lmu[ro] >> 1; k++) {
+ s16 a, b, c;
+
+ if (!(smu[ro * num + k] && dmu[i]))
+ continue;
+
+ a = index_of[dmu[i]];
+ b = index_of[dmu[ro]];
+ c = index_of[smu[ro * num + k]];
+ tmp = a + (cw_len - b) + c;
+ a = alpha_to[tmp % cw_len];
+ smu[(i + 1) * num + (k + diff)] = a;
+ }
+
+ for (k = 0; k <= lmu[i] >> 1; k++)
+ smu[(i + 1) * num + k] ^= smu[i * num + k];
+ }
+
+ /* End Computing Sigma (Mu+1) and L(mu) */
+ /* In either case compute delta */
+ delta[i + 1] = (mu[i + 1] * 2 - lmu[i + 1]) >> 1;
+
+ /* Do not compute discrepancy for the last iteration */
+ if (i >= strength)
+ continue;
+
+ for (k = 0; k <= (lmu[i + 1] >> 1); k++) {
+ tmp = 2 * (i - 1);
+ if (k == 0) {
+ dmu[i + 1] = si[tmp + 3];
+ } else if (smu[(i + 1) * num + k] && si[tmp + 3 - k]) {
+ s16 a, b, c;
+
+ a = index_of[smu[(i + 1) * num + k]];
+ b = si[2 * (i - 1) + 3 - k];
+ c = index_of[b];
+ tmp = a + c;
+ tmp %= cw_len;
+ dmu[i + 1] = alpha_to[tmp] ^ dmu[i + 1];
+ }
+ }
+ }
+}
+
+static int atmel_pmecc_err_location(struct atmel_pmecc_user *user)
+{
+ int sector_size = get_sectorsize(user);
+ int degree = sector_size == 512 ? 13 : 14;
+ struct atmel_pmecc *pmecc = user->pmecc;
+ int strength = get_strength(user);
+ int ret, roots_nbr, i, err_nbr = 0;
+ int num = (2 * strength) + 1;
+ s16 *smu = user->smu;
+ u32 val;
+
+ writel(PMERRLOC_DISABLE, pmecc->regs.errloc + ATMEL_PMERRLOC_ELDIS);
+
+ for (i = 0; i <= user->lmu[strength + 1] >> 1; i++) {
+ writel_relaxed(smu[(strength + 1) * num + i],
+ pmecc->regs.errloc + ATMEL_PMERRLOC_SIGMA(i));
+ err_nbr++;
+ }
+
+ val = (err_nbr - 1) << 16;
+ if (sector_size == 1024)
+ val |= 1;
+
+ writel(val, pmecc->regs.errloc + ATMEL_PMERRLOC_ELCFG);
+ writel((sector_size * 8) + (degree * strength),
+ pmecc->regs.errloc + ATMEL_PMERRLOC_ELEN);
+
+ ret = readl_relaxed_poll_timeout(pmecc->regs.errloc +
+ ATMEL_PMERRLOC_ELISR,
+ val, val & PMERRLOC_CALC_DONE,
+ PMECC_MAX_TIMEOUT_MS * 1000);
+ if (ret) {
+ dev_err(pmecc->dev,
+ "PMECC: Timeout to calculate error location.\n");
+ return ret;
+ }
+
+ roots_nbr = (val & PMERRLOC_ERR_NUM_MASK) >> 8;
+ /* Number of roots == degree of smu hence <= cap */
+ if (roots_nbr == user->lmu[strength + 1] >> 1)
+ return err_nbr - 1;
+
+ /*
+ * Number of roots does not match the degree of smu
+ * unable to correct error.
+ */
+ return -EBADMSG;
+}
+
+int atmel_pmecc_correct_sector(struct atmel_pmecc_user *user, int sector,
+ void *data, void *ecc)
+{
+ struct atmel_pmecc *pmecc = user->pmecc;
+ int sectorsize = get_sectorsize(user);
+ int eccbytes = user->eccbytes;
+ int i, nerrors;
+
+ if (!(user->isr & BIT(sector)))
+ return 0;
+
+ atmel_pmecc_gen_syndrome(user, sector);
+ atmel_pmecc_substitute(user);
+ atmel_pmecc_get_sigma(user);
+
+ nerrors = atmel_pmecc_err_location(user);
+ if (nerrors < 0)
+ return nerrors;
+
+ for (i = 0; i < nerrors; i++) {
+ const char *area;
+ int byte, bit;
+ u32 errpos;
+ u8 *ptr;
+
+ errpos = readl_relaxed(pmecc->regs.errloc +
+ ATMEL_PMERRLOC_EL(pmecc->caps->el_offset, i));
+ errpos--;
+
+ byte = errpos / 8;
+ bit = errpos % 8;
+
+ if (byte < sectorsize) {
+ ptr = data + byte;
+ area = "data";
+ } else if (byte < sectorsize + eccbytes) {
+ ptr = ecc + byte - sectorsize;
+ area = "ECC";
+ } else {
+ dev_dbg(pmecc->dev,
+ "Invalid errpos value (%d, max is %d)\n",
+ errpos, (sectorsize + eccbytes) * 8);
+ return -EINVAL;
+ }
+
+ dev_dbg(pmecc->dev,
+ "Bit flip in %s area, byte %d: 0x%02x -> 0x%02x\n",
+ area, byte, *ptr, (unsigned int)(*ptr ^ BIT(bit)));
+
+ *ptr ^= BIT(bit);
+ }
+
+ return nerrors;
+}
+EXPORT_SYMBOL_GPL(atmel_pmecc_correct_sector);
+
+bool atmel_pmecc_correct_erased_chunks(struct atmel_pmecc_user *user)
+{
+ return user->pmecc->caps->correct_erased_chunks;
+}
+EXPORT_SYMBOL_GPL(atmel_pmecc_correct_erased_chunks);
+
+void atmel_pmecc_get_generated_eccbytes(struct atmel_pmecc_user *user,
+ int sector, void *ecc)
+{
+ struct atmel_pmecc *pmecc = user->pmecc;
+ u8 *ptr = ecc;
+ int i;
+
+ for (i = 0; i < user->eccbytes; i++)
+ ptr[i] = readb_relaxed(pmecc->regs.base +
+ ATMEL_PMECC_ECC(sector, i));
+}
+EXPORT_SYMBOL_GPL(atmel_pmecc_get_generated_eccbytes);
+
+void atmel_pmecc_reset(struct atmel_pmecc *pmecc)
+{
+ writel(PMECC_CTRL_RST, pmecc->regs.base + ATMEL_PMECC_CTRL);
+ writel(PMECC_CTRL_DISABLE, pmecc->regs.base + ATMEL_PMECC_CTRL);
+}
+EXPORT_SYMBOL_GPL(atmel_pmecc_reset);
+
+int atmel_pmecc_enable(struct atmel_pmecc_user *user, int op)
+{
+ struct atmel_pmecc *pmecc = user->pmecc;
+ u32 cfg;
+
+ if (op != NAND_ECC_READ && op != NAND_ECC_WRITE) {
+ dev_err(pmecc->dev, "Bad ECC operation!");
+ return -EINVAL;
+ }
+
+ mutex_lock(&user->pmecc->lock);
+
+ cfg = user->cache.cfg;
+ if (op == NAND_ECC_WRITE)
+ cfg |= PMECC_CFG_WRITE_OP;
+ else
+ cfg |= PMECC_CFG_AUTO_ENABLE;
+
+ writel(cfg, pmecc->regs.base + ATMEL_PMECC_CFG);
+ writel(user->cache.sarea, pmecc->regs.base + ATMEL_PMECC_SAREA);
+ writel(user->cache.saddr, pmecc->regs.base + ATMEL_PMECC_SADDR);
+ writel(user->cache.eaddr, pmecc->regs.base + ATMEL_PMECC_EADDR);
+
+ writel(PMECC_CTRL_ENABLE, pmecc->regs.base + ATMEL_PMECC_CTRL);
+ writel(PMECC_CTRL_DATA, pmecc->regs.base + ATMEL_PMECC_CTRL);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(atmel_pmecc_enable);
+
+void atmel_pmecc_disable(struct atmel_pmecc_user *user)
+{
+ atmel_pmecc_reset(user->pmecc);
+ mutex_unlock(&user->pmecc->lock);
+}
+EXPORT_SYMBOL_GPL(atmel_pmecc_disable);
+
+int atmel_pmecc_wait_rdy(struct atmel_pmecc_user *user)
+{
+ struct atmel_pmecc *pmecc = user->pmecc;
+ u32 status;
+ int ret;
+
+ ret = readl_relaxed_poll_timeout(pmecc->regs.base +
+ ATMEL_PMECC_SR,
+ status, !(status & PMECC_SR_BUSY),
+ PMECC_MAX_TIMEOUT_MS * 1000);
+ if (ret) {
+ dev_err(pmecc->dev,
+ "Timeout while waiting for PMECC ready.\n");
+ return ret;
+ }
+
+ user->isr = readl_relaxed(pmecc->regs.base + ATMEL_PMECC_ISR);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(atmel_pmecc_wait_rdy);
+
+#define ATMEL_BASE_PMECC 0xffffe000
+#define ATMEL_BASE_PMERRLOC 0xffffe600
+
+static struct atmel_pmecc *
+atmel_pmecc_create(struct udevice *dev,
+ const struct atmel_pmecc_caps *caps,
+ int pmecc_res_idx, int errloc_res_idx,
+ int timing_res_idx)
+{
+ struct atmel_pmecc *pmecc;
+ struct resource res;
+
+ pmecc = devm_kzalloc(dev, sizeof(*pmecc), GFP_KERNEL);
+ if (!pmecc)
+ return ERR_PTR(-ENOMEM);
+
+ pmecc->caps = caps;
+ pmecc->dev = dev;
+ mutex_init(&pmecc->lock);
+
+ ofnode_read_resource(dev->node_, 0, &res);
+ pmecc->regs.base = (void *)res.start;
+ ofnode_read_resource(dev->node_, 1, &res);
+ pmecc->regs.errloc = (void *)res.start;
+
+ pmecc->regs.timing = 0;
+
+ /* Disable all interrupts before registering the PMECC handler. */
+ writel(0xffffffff, pmecc->regs.base + ATMEL_PMECC_IDR);
+ atmel_pmecc_reset(pmecc);
+
+ return pmecc;
+}
+
+static void devm_atmel_pmecc_put(struct udevice *dev, void *res)
+{
+}
+
+static struct atmel_pmecc *atmel_pmecc_get_by_node(struct udevice *dev)
+{
+ struct atmel_pmecc *pmecc, **ptr;
+ int ret;
+
+ pmecc = dev_get_plat(dev);
+ if (!pmecc) {
+ ret = -EPROBE_DEFER;
+ goto err_put_device;
+ }
+
+ ptr = devres_alloc(devm_atmel_pmecc_put, sizeof(*ptr), GFP_KERNEL);
+ if (!ptr) {
+ ret = -ENOMEM;
+ goto err_put_device;
+ }
+
+ *ptr = pmecc;
+
+ devres_add(dev, ptr);
+
+ return pmecc;
+
+err_put_device:
+ return ERR_PTR(ret);
+}
+
+static const int atmel_pmecc_strengths[] = { 2, 4, 8, 12, 24, 32 };
+
+static struct atmel_pmecc_caps at91sam9g45_caps = {
+ .strengths = atmel_pmecc_strengths,
+ .nstrengths = 5,
+ .el_offset = 0x8c,
+};
+
+static struct atmel_pmecc_caps sama5d4_caps = {
+ .strengths = atmel_pmecc_strengths,
+ .nstrengths = 5,
+ .el_offset = 0x8c,
+ .correct_erased_chunks = true,
+};
+
+static struct atmel_pmecc_caps sama5d2_caps = {
+ .strengths = atmel_pmecc_strengths,
+ .nstrengths = 6,
+ .el_offset = 0xac,
+ .correct_erased_chunks = true,
+};
+
+struct atmel_pmecc *devm_atmel_pmecc_get(struct udevice *userdev)
+{
+ struct atmel_pmecc *pmecc;
+ struct ofnode_phandle_args args;
+ struct udevice *pdev;
+ int ret;
+
+ if (!userdev)
+ return ERR_PTR(-EINVAL);
+
+ ret = ofnode_parse_phandle_with_args(userdev->node_,
+ "ecc-engine",
+ NULL, 0, 0, &args);
+ ret = uclass_get_device_by_ofnode(UCLASS_MTD, args.node, &pdev);
+ if (ret)
+ return NULL;
+
+ pmecc = atmel_pmecc_get_by_node(pdev);
+
+ /* TODO:
+ * Support old DT bindings
+ */
+
+ return pmecc;
+}
+EXPORT_SYMBOL(devm_atmel_pmecc_get);
+
+static const struct udevice_id atmel_pmecc_match[] = {
+ { .compatible = "atmel,at91sam9g45-pmecc", (ulong)&at91sam9g45_caps },
+ { .compatible = "atmel,sama5d4-pmecc", (ulong)&sama5d4_caps },
+ { .compatible = "atmel,sama5d2-pmecc", (ulong)&sama5d2_caps },
+ { /* sentinel */ }
+};
+
+static int atmel_pmecc_probe(struct udevice *dev)
+{
+ const struct atmel_pmecc_caps *caps;
+ struct atmel_pmecc *pmecc;
+
+ caps = (struct atmel_pmecc_caps *)dev_get_driver_data(dev);
+ if (!caps) {
+ dev_err(dev, "Invalid caps\n");
+ return -EINVAL;
+ }
+
+ pmecc = atmel_pmecc_create(dev, caps, 0, 1, 2);
+ if (IS_ERR(pmecc))
+ return PTR_ERR(pmecc);
+
+ dev->plat_ = pmecc;
+
+ return 0;
+}
+
+U_BOOT_DRIVER(atmel_pmecc) = {
+ .name = "atmel-pmecc",
+ .id = UCLASS_MTD,
+ .of_match = atmel_pmecc_match,
+ .probe = atmel_pmecc_probe,
+};
diff --git a/drivers/mtd/nand/raw/atmel/pmecc.h b/drivers/mtd/nand/raw/atmel/pmecc.h
new file mode 100644
index 00000000000..43f96b2f168
--- /dev/null
+++ b/drivers/mtd/nand/raw/atmel/pmecc.h
@@ -0,0 +1,94 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * © Copyright 2016 ATMEL
+ * © Copyright 2016 Free Electrons
+ *
+ * Author: Boris Brezillon <boris.brezillon@free-electrons.com>
+ *
+ * Derived from the atmel_nand.c driver which contained the following
+ * copyrights:
+ *
+ * Copyright © 2003 Rick Bronson
+ *
+ * Derived from drivers/mtd/nand/autcpu12.c (removed in v3.8)
+ * Copyright © 2001 Thomas Gleixner (gleixner@autronix.de)
+ *
+ * Derived from drivers/mtd/spia.c (removed in v3.8)
+ * Copyright © 2000 Steven J. Hill (sjhill@cotw.com)
+ *
+ *
+ * Add Hardware ECC support for AT91SAM9260 / AT91SAM9263
+ * Richard Genoud (richard.genoud@gmail.com), Adeneo Copyright © 2007
+ *
+ * Derived from Das U-Boot source code
+ * (u-boot-1.1.5/board/atmel/at91sam9263ek/nand.c)
+ * © Copyright 2006 ATMEL Rousset, Lacressonniere Nicolas
+ *
+ * Add Programmable Multibit ECC support for various AT91 SoC
+ * © Copyright 2012 ATMEL, Hong Xu
+ *
+ * Add Nand Flash Controller support for SAMA5 SoC
+ * © Copyright 2013 ATMEL, Josh Wu (josh.wu@atmel.com)
+ */
+
+#ifndef ATMEL_PMECC_H
+#define ATMEL_PMECC_H
+
+#define ATMEL_PMECC_MAXIMIZE_ECC_STRENGTH 0
+#define ATMEL_PMECC_SECTOR_SIZE_AUTO 0
+#define ATMEL_PMECC_OOBOFFSET_AUTO -1
+
+struct atmel_pmecc_user_req {
+ int pagesize;
+ int oobsize;
+ struct {
+ int strength;
+ int bytes;
+ int sectorsize;
+ int nsectors;
+ int ooboffset;
+ } ecc;
+};
+
+struct atmel_pmecc_suspend_ctx {
+ u32 setup;
+ u32 pulse;
+ u32 cycle;
+ u32 timings;
+ u32 mode;
+};
+
+struct atmel_pmecc {
+ struct udevice *dev;
+ const struct atmel_pmecc_caps *caps;
+
+ struct {
+ void __iomem *base;
+ void __iomem *errloc;
+ void __iomem *timing;
+ } regs;
+
+ /* Mutex used for pmecc enable/disable */
+ struct mutex lock;
+
+ struct atmel_pmecc_suspend_ctx suspend;
+};
+
+struct atmel_pmecc *devm_atmel_pmecc_get(struct udevice *dev);
+
+struct atmel_pmecc_user *
+atmel_pmecc_create_user(struct atmel_pmecc *pmecc,
+ struct atmel_pmecc_user_req *req);
+void atmel_pmecc_destroy_user(struct atmel_pmecc_user *user);
+
+void atmel_pmecc_reset(struct atmel_pmecc *pmecc);
+int atmel_pmecc_enable(struct atmel_pmecc_user *user, int op);
+void atmel_pmecc_disable(struct atmel_pmecc_user *user);
+int atmel_pmecc_wait_rdy(struct atmel_pmecc_user *user);
+int atmel_pmecc_correct_sector(struct atmel_pmecc_user *user, int sector,
+ void *data, void *ecc);
+bool atmel_pmecc_correct_erased_chunks(struct atmel_pmecc_user *user);
+void atmel_pmecc_get_generated_eccbytes(struct atmel_pmecc_user *user,
+ int sector, void *ecc);
+
+#endif /* ATMEL_PMECC_H */
diff --git a/drivers/reset/Kconfig b/drivers/reset/Kconfig
index 4cb0ba08508..e4039d74744 100644
--- a/drivers/reset/Kconfig
+++ b/drivers/reset/Kconfig
@@ -211,4 +211,12 @@ config RESET_DRA7
help
Support for TI DRA7-RESET subsystem. Basic Assert/Deassert
is supported.
+
+config RESET_AT91
+ bool "Enable support for Microchip/Atmel Reset Controller driver"
+ depends on DM_RESET && ARCH_AT91
+ help
+ This enables the Reset Controller driver support for Microchip/Atmel
+ SoCs. Mainly used to expose assert/deassert methods to other drivers
+ that require it.
endmenu
diff --git a/drivers/reset/Makefile b/drivers/reset/Makefile
index 0620b628090..6c8b45ecbab 100644
--- a/drivers/reset/Makefile
+++ b/drivers/reset/Makefile
@@ -31,3 +31,4 @@ obj-$(CONFIG_RESET_RASPBERRYPI) += reset-raspberrypi.o
obj-$(CONFIG_RESET_SCMI) += reset-scmi.o
obj-$(CONFIG_RESET_ZYNQMP) += reset-zynqmp.o
obj-$(CONFIG_RESET_DRA7) += reset-dra7.o
+obj-$(CONFIG_RESET_AT91) += reset-at91.o
diff --git a/drivers/reset/reset-at91.c b/drivers/reset/reset-at91.c
new file mode 100644
index 00000000000..165c87acdc4
--- /dev/null
+++ b/drivers/reset/reset-at91.c
@@ -0,0 +1,141 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Support for Atmel/Microchip Reset Controller.
+ *
+ * Copyright (C) 2022 Microchip Technology Inc. and its subsidiaries
+ *
+ * Author: Sergiu Moga <sergiu.moga@microchip.com>
+ */
+
+#include <clk.h>
+#include <asm/io.h>
+#include <dm.h>
+#include <dm/lists.h>
+#include <reset-uclass.h>
+#include <asm/arch/at91_rstc.h>
+#include <dt-bindings/reset/sama7g5-reset.h>
+
+struct at91_reset {
+ void __iomem *dev_base;
+ struct at91_reset_data *data;
+};
+
+struct at91_reset_data {
+ u32 n_device_reset;
+ u8 device_reset_min_id;
+ u8 device_reset_max_id;
+};
+
+static const struct at91_reset_data sama7g5_data = {
+ .n_device_reset = 3,
+ .device_reset_min_id = SAMA7G5_RESET_USB_PHY1,
+ .device_reset_max_id = SAMA7G5_RESET_USB_PHY3,
+};
+
+static int at91_rst_update(struct at91_reset *reset, unsigned long id,
+ bool assert)
+{
+ u32 val;
+
+ if (!reset->dev_base)
+ return 0;
+
+ val = readl(reset->dev_base);
+ if (assert)
+ val |= BIT(id);
+ else
+ val &= ~BIT(id);
+ writel(val, reset->dev_base);
+
+ return 0;
+}
+
+static int at91_reset_of_xlate(struct reset_ctl *reset_ctl,
+ struct ofnode_phandle_args *args)
+{
+ struct at91_reset *reset = dev_get_priv(reset_ctl->dev);
+
+ if (!reset->data->n_device_reset ||
+ args->args[0] < reset->data->device_reset_min_id ||
+ args->args[0] > reset->data->device_reset_max_id)
+ return -EINVAL;
+
+ reset_ctl->id = args->args[0];
+
+ return 0;
+}
+
+static int at91_rst_assert(struct reset_ctl *reset_ctl)
+{
+ struct at91_reset *reset = dev_get_priv(reset_ctl->dev);
+
+ return at91_rst_update(reset, reset_ctl->id, true);
+}
+
+static int at91_rst_deassert(struct reset_ctl *reset_ctl)
+{
+ struct at91_reset *reset = dev_get_priv(reset_ctl->dev);
+
+ return at91_rst_update(reset, reset_ctl->id, false);
+}
+
+struct reset_ops at91_reset_ops = {
+ .of_xlate = at91_reset_of_xlate,
+ .rst_assert = at91_rst_assert,
+ .rst_deassert = at91_rst_deassert,
+};
+
+static int at91_reset_probe(struct udevice *dev)
+{
+ struct at91_reset *reset = dev_get_priv(dev);
+ struct clk sclk;
+ int ret;
+
+ reset->data = (struct at91_reset_data *)dev_get_driver_data(dev);
+ reset->dev_base = dev_remap_addr_index(dev, 1);
+ if (reset->data && reset->data->n_device_reset && !reset->dev_base)
+ return -EINVAL;
+
+ ret = clk_get_by_index(dev, 0, &sclk);
+ if (ret)
+ return ret;
+
+ return clk_prepare_enable(&sclk);
+}
+
+static int at91_reset_bind(struct udevice *dev)
+{
+ struct udevice *at91_sysreset;
+
+ if (CONFIG_IS_ENABLED(SYSRESET_AT91))
+ return device_bind_driver_to_node(dev, "at91_sysreset",
+ "at91_sysreset",
+ dev_ofnode(dev),
+ &at91_sysreset);
+
+ return 0;
+}
+
+static const struct udevice_id at91_reset_ids[] = {
+ {
+ .compatible = "microchip,sama7g5-rstc",
+ .data = (ulong)&sama7g5_data,
+ },
+ {
+ .compatible = "atmel,sama5d3-rstc",
+ },
+ {
+ .compatible = "microchip,sam9x60-rstc",
+ },
+ { }
+};
+
+U_BOOT_DRIVER(at91_reset) = {
+ .name = "at91_reset",
+ .id = UCLASS_RESET,
+ .of_match = at91_reset_ids,
+ .bind = at91_reset_bind,
+ .probe = at91_reset_probe,
+ .priv_auto = sizeof(struct at91_reset),
+ .ops = &at91_reset_ops,
+};
diff --git a/drivers/sysreset/sysreset_at91.c b/drivers/sysreset/sysreset_at91.c
index 24b87ee987d..fc85f31ebf0 100644
--- a/drivers/sysreset/sysreset_at91.c
+++ b/drivers/sysreset/sysreset_at91.c
@@ -56,16 +56,9 @@ static struct sysreset_ops at91_sysreset = {
.request = at91_sysreset_request,
};
-static const struct udevice_id a91_sysreset_ids[] = {
- { .compatible = "atmel,sama5d3-rstc" },
- { .compatible = "microchip,sam9x60-rstc" },
- { }
-};
-
U_BOOT_DRIVER(sysreset_at91) = {
.id = UCLASS_SYSRESET,
- .name = "at91_reset",
+ .name = "at91_sysreset",
.ops = &at91_sysreset,
.probe = at91_sysreset_probe,
- .of_match = a91_sysreset_ids,
};
diff --git a/include/configs/sam9x60ek.h b/include/configs/sam9x60ek.h
index 2c761821efb..af504e0efab 100644
--- a/include/configs/sam9x60ek.h
+++ b/include/configs/sam9x60ek.h
@@ -26,13 +26,4 @@
#define CFG_SYS_SDRAM_BASE 0x20000000
#define CFG_SYS_SDRAM_SIZE 0x10000000 /* 256 megs */
-/* NAND flash */
-#ifdef CONFIG_CMD_NAND
-#define CFG_SYS_NAND_BASE 0x40000000
-#define CFG_SYS_NAND_MASK_ALE BIT(21)
-#define CFG_SYS_NAND_MASK_CLE BIT(22)
-#define CFG_SYS_NAND_ENABLE_PIN AT91_PIN_PD4
-#define CFG_SYS_NAND_READY_PIN AT91_PIN_PD5
-#endif
-
#endif
diff --git a/include/dt-bindings/clk/at91.h b/include/dt-bindings/clk/at91.h
index e30756b2804..a178b94157b 100644
--- a/include/dt-bindings/clk/at91.h
+++ b/include/dt-bindings/clk/at91.h
@@ -18,5 +18,10 @@
#define PMC_TYPE_PERIPHERAL 3
#define PMC_TYPE_GCK 4
#define PMC_TYPE_SLOW 5
+#define USB_UTMI 6
+
+#define USB_UTMI1 0
+#define USB_UTMI2 1
+#define USB_UTMI3 2
#endif
diff --git a/include/dt-bindings/mfd/at91-usart.h b/include/dt-bindings/mfd/at91-usart.h
new file mode 100644
index 00000000000..2de5bc312e1
--- /dev/null
+++ b/include/dt-bindings/mfd/at91-usart.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * This header provides macros for AT91 USART DT bindings.
+ *
+ * Copyright (C) 2018 Microchip Technology
+ *
+ * Author: Radu Pirea <radu.pirea@microchip.com>
+ *
+ */
+
+#ifndef __DT_BINDINGS_AT91_USART_H__
+#define __DT_BINDINGS_AT91_USART_H__
+
+#define AT91_USART_MODE_SERIAL 0
+#define AT91_USART_MODE_SPI 1
+
+#endif /* __DT_BINDINGS_AT91_USART_H__ */
diff --git a/include/dt-bindings/reset/sama7g5-reset.h b/include/dt-bindings/reset/sama7g5-reset.h
new file mode 100644
index 00000000000..2116f41d04e
--- /dev/null
+++ b/include/dt-bindings/reset/sama7g5-reset.h
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
+
+#ifndef __DT_BINDINGS_RESET_SAMA7G5_H
+#define __DT_BINDINGS_RESET_SAMA7G5_H
+
+#define SAMA7G5_RESET_USB_PHY1 4
+#define SAMA7G5_RESET_USB_PHY2 5
+#define SAMA7G5_RESET_USB_PHY3 6
+
+#endif /* __DT_BINDINGS_RESET_SAMA7G5_H */
diff --git a/include/linux/mfd/syscon/atmel-matrix.h b/include/linux/mfd/syscon/atmel-matrix.h
new file mode 100644
index 00000000000..dd228cab67a
--- /dev/null
+++ b/include/linux/mfd/syscon/atmel-matrix.h
@@ -0,0 +1,112 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (C) 2014 Atmel Corporation.
+ *
+ * Memory Controllers (MATRIX, EBI) - System peripherals registers.
+ */
+
+#ifndef _LINUX_MFD_SYSCON_ATMEL_MATRIX_H
+#define _LINUX_MFD_SYSCON_ATMEL_MATRIX_H
+
+#define AT91SAM9260_MATRIX_MCFG 0x00
+#define AT91SAM9260_MATRIX_SCFG 0x40
+#define AT91SAM9260_MATRIX_PRS 0x80
+#define AT91SAM9260_MATRIX_MRCR 0x100
+#define AT91SAM9260_MATRIX_EBICSA 0x11c
+
+#define AT91SAM9261_MATRIX_MRCR 0x0
+#define AT91SAM9261_MATRIX_SCFG 0x4
+#define AT91SAM9261_MATRIX_TCR 0x24
+#define AT91SAM9261_MATRIX_EBICSA 0x30
+#define AT91SAM9261_MATRIX_USBPUCR 0x34
+
+#define AT91SAM9263_MATRIX_MCFG 0x00
+#define AT91SAM9263_MATRIX_SCFG 0x40
+#define AT91SAM9263_MATRIX_PRS 0x80
+#define AT91SAM9263_MATRIX_MRCR 0x100
+#define AT91SAM9263_MATRIX_TCR 0x114
+#define AT91SAM9263_MATRIX_EBI0CSA 0x120
+#define AT91SAM9263_MATRIX_EBI1CSA 0x124
+
+#define AT91SAM9RL_MATRIX_MCFG 0x00
+#define AT91SAM9RL_MATRIX_SCFG 0x40
+#define AT91SAM9RL_MATRIX_PRS 0x80
+#define AT91SAM9RL_MATRIX_MRCR 0x100
+#define AT91SAM9RL_MATRIX_TCR 0x114
+#define AT91SAM9RL_MATRIX_EBICSA 0x120
+
+#define AT91SAM9G45_MATRIX_MCFG 0x00
+#define AT91SAM9G45_MATRIX_SCFG 0x40
+#define AT91SAM9G45_MATRIX_PRS 0x80
+#define AT91SAM9G45_MATRIX_MRCR 0x100
+#define AT91SAM9G45_MATRIX_TCR 0x110
+#define AT91SAM9G45_MATRIX_DDRMPR 0x118
+#define AT91SAM9G45_MATRIX_EBICSA 0x128
+
+#define AT91SAM9N12_MATRIX_MCFG 0x00
+#define AT91SAM9N12_MATRIX_SCFG 0x40
+#define AT91SAM9N12_MATRIX_PRS 0x80
+#define AT91SAM9N12_MATRIX_MRCR 0x100
+#define AT91SAM9N12_MATRIX_EBICSA 0x118
+
+#define AT91SAM9X5_MATRIX_MCFG 0x00
+#define AT91SAM9X5_MATRIX_SCFG 0x40
+#define AT91SAM9X5_MATRIX_PRS 0x80
+#define AT91SAM9X5_MATRIX_MRCR 0x100
+#define AT91SAM9X5_MATRIX_EBICSA 0x120
+
+#define SAMA5D3_MATRIX_MCFG 0x00
+#define SAMA5D3_MATRIX_SCFG 0x40
+#define SAMA5D3_MATRIX_PRS 0x80
+#define SAMA5D3_MATRIX_MRCR 0x100
+
+#define AT91_MATRIX_MCFG(o, x) ((o) + ((x) * 0x4))
+#define AT91_MATRIX_ULBT GENMASK(2, 0)
+#define AT91_MATRIX_ULBT_INFINITE (0 << 0)
+#define AT91_MATRIX_ULBT_SINGLE (1 << 0)
+#define AT91_MATRIX_ULBT_FOUR (2 << 0)
+#define AT91_MATRIX_ULBT_EIGHT (3 << 0)
+#define AT91_MATRIX_ULBT_SIXTEEN (4 << 0)
+
+#define AT91_MATRIX_SCFG(o, x) ((o) + ((x) * 0x4))
+#define AT91_MATRIX_SLOT_CYCLE GENMASK(7, 0)
+#define AT91_MATRIX_DEFMSTR_TYPE GENMASK(17, 16)
+#define AT91_MATRIX_DEFMSTR_TYPE_NONE (0 << 16)
+#define AT91_MATRIX_DEFMSTR_TYPE_LAST (1 << 16)
+#define AT91_MATRIX_DEFMSTR_TYPE_FIXED (2 << 16)
+#define AT91_MATRIX_FIXED_DEFMSTR GENMASK(20, 18)
+#define AT91_MATRIX_ARBT GENMASK(25, 24)
+#define AT91_MATRIX_ARBT_ROUND_ROBIN (0 << 24)
+#define AT91_MATRIX_ARBT_FIXED_PRIORITY (1 << 24)
+
+#define AT91_MATRIX_ITCM_SIZE GENMASK(3, 0)
+#define AT91_MATRIX_ITCM_0 (0 << 0)
+#define AT91_MATRIX_ITCM_16 (5 << 0)
+#define AT91_MATRIX_ITCM_32 (6 << 0)
+#define AT91_MATRIX_ITCM_64 (7 << 0)
+#define AT91_MATRIX_DTCM_SIZE GENMASK(7, 4)
+#define AT91_MATRIX_DTCM_0 (0 << 4)
+#define AT91_MATRIX_DTCM_16 (5 << 4)
+#define AT91_MATRIX_DTCM_32 (6 << 4)
+#define AT91_MATRIX_DTCM_64 (7 << 4)
+
+#define AT91_MATRIX_PRAS(o, x) ((o) + ((x) * 0x8))
+#define AT91_MATRIX_PRBS(o, x) ((o) + ((x) * 0x8) + 0x4)
+#define AT91_MATRIX_MPR(x) GENMASK(((x) * 0x4) + 1, ((x) * 0x4))
+
+#define AT91_MATRIX_RCB(x) BIT(x)
+
+#define AT91_MATRIX_CSA(cs, val) ((val) << (cs))
+#define AT91_MATRIX_DBPUC BIT(8)
+#define AT91_MATRIX_DBPDC BIT(9)
+#define AT91_MATRIX_VDDIOMSEL BIT(16)
+#define AT91_MATRIX_VDDIOMSEL_1_8V (0 << 16)
+#define AT91_MATRIX_VDDIOMSEL_3_3V (1 << 16)
+#define AT91_MATRIX_EBI_IOSR BIT(17)
+#define AT91_MATRIX_DDR_IOSR BIT(18)
+#define AT91_MATRIX_NFD0_SELECT BIT(24)
+#define AT91_MATRIX_DDR_MP_EN BIT(25)
+
+#define AT91_MATRIX_USBPUCR_PUON BIT(30)
+
+#endif /* _LINUX_MFD_SYSCON_ATMEL_MATRIX_H */
diff --git a/include/linux/mfd/syscon/atmel-smc.h b/include/linux/mfd/syscon/atmel-smc.h
new file mode 100644
index 00000000000..74be5a199fe
--- /dev/null
+++ b/include/linux/mfd/syscon/atmel-smc.h
@@ -0,0 +1,119 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Atmel SMC (Static Memory Controller) register offsets and bit definitions.
+ *
+ * Copyright (C) 2014 Atmel
+ * Copyright (C) 2014 Free Electrons
+ *
+ * Author: Boris Brezillon <boris.brezillon@free-electrons.com>
+ */
+
+#ifndef _LINUX_MFD_SYSCON_ATMEL_SMC_H_
+#define _LINUX_MFD_SYSCON_ATMEL_SMC_H_
+
+#include <linux/kernel.h>
+#include <dm/ofnode.h>
+#include <regmap.h>
+
+#define ATMEL_SMC_SETUP(cs) (((cs) * 0x10))
+#define ATMEL_HSMC_SETUP(layout, cs) \
+ ((layout)->timing_regs_offset + ((cs) * 0x14))
+#define ATMEL_SMC_PULSE(cs) (((cs) * 0x10) + 0x4)
+#define ATMEL_HSMC_PULSE(layout, cs) \
+ ((layout)->timing_regs_offset + ((cs) * 0x14) + 0x4)
+#define ATMEL_SMC_CYCLE(cs) (((cs) * 0x10) + 0x8)
+#define ATMEL_HSMC_CYCLE(layout, cs) \
+ ((layout)->timing_regs_offset + ((cs) * 0x14) + 0x8)
+#define ATMEL_SMC_NWE_SHIFT 0
+#define ATMEL_SMC_NCS_WR_SHIFT 8
+#define ATMEL_SMC_NRD_SHIFT 16
+#define ATMEL_SMC_NCS_RD_SHIFT 24
+
+#define ATMEL_SMC_MODE(cs) (((cs) * 0x10) + 0xc)
+#define ATMEL_HSMC_MODE(layout, cs) \
+ ((layout)->timing_regs_offset + ((cs) * 0x14) + 0x10)
+#define ATMEL_SMC_MODE_READMODE_MASK BIT(0)
+#define ATMEL_SMC_MODE_READMODE_NCS (0 << 0)
+#define ATMEL_SMC_MODE_READMODE_NRD (1 << 0)
+#define ATMEL_SMC_MODE_WRITEMODE_MASK BIT(1)
+#define ATMEL_SMC_MODE_WRITEMODE_NCS (0 << 1)
+#define ATMEL_SMC_MODE_WRITEMODE_NWE (1 << 1)
+#define ATMEL_SMC_MODE_EXNWMODE_MASK GENMASK(5, 4)
+#define ATMEL_SMC_MODE_EXNWMODE_DISABLE (0 << 4)
+#define ATMEL_SMC_MODE_EXNWMODE_FROZEN (2 << 4)
+#define ATMEL_SMC_MODE_EXNWMODE_READY (3 << 4)
+#define ATMEL_SMC_MODE_BAT_MASK BIT(8)
+#define ATMEL_SMC_MODE_BAT_SELECT (0 << 8)
+#define ATMEL_SMC_MODE_BAT_WRITE (1 << 8)
+#define ATMEL_SMC_MODE_DBW_MASK GENMASK(13, 12)
+#define ATMEL_SMC_MODE_DBW_8 (0 << 12)
+#define ATMEL_SMC_MODE_DBW_16 (1 << 12)
+#define ATMEL_SMC_MODE_DBW_32 (2 << 12)
+#define ATMEL_SMC_MODE_TDF_MASK GENMASK(19, 16)
+#define ATMEL_SMC_MODE_TDF(x) (((x) - 1) << 16)
+#define ATMEL_SMC_MODE_TDF_MAX 16
+#define ATMEL_SMC_MODE_TDF_MIN 1
+#define ATMEL_SMC_MODE_TDFMODE_OPTIMIZED BIT(20)
+#define ATMEL_SMC_MODE_PMEN BIT(24)
+#define ATMEL_SMC_MODE_PS_MASK GENMASK(29, 28)
+#define ATMEL_SMC_MODE_PS_4 (0 << 28)
+#define ATMEL_SMC_MODE_PS_8 (1 << 28)
+#define ATMEL_SMC_MODE_PS_16 (2 << 28)
+#define ATMEL_SMC_MODE_PS_32 (3 << 28)
+
+#define ATMEL_HSMC_TIMINGS(layout, cs) \
+ ((layout)->timing_regs_offset + ((cs) * 0x14) + 0xc)
+#define ATMEL_HSMC_TIMINGS_OCMS BIT(12)
+#define ATMEL_HSMC_TIMINGS_RBNSEL(x) ((x) << 28)
+#define ATMEL_HSMC_TIMINGS_NFSEL BIT(31)
+#define ATMEL_HSMC_TIMINGS_TCLR_SHIFT 0
+#define ATMEL_HSMC_TIMINGS_TADL_SHIFT 4
+#define ATMEL_HSMC_TIMINGS_TAR_SHIFT 8
+#define ATMEL_HSMC_TIMINGS_TRR_SHIFT 16
+#define ATMEL_HSMC_TIMINGS_TWB_SHIFT 24
+
+struct atmel_hsmc_reg_layout {
+ unsigned int timing_regs_offset;
+};
+
+/**
+ * struct atmel_smc_cs_conf - SMC CS config as described in the datasheet.
+ * @setup: NCS/NWE/NRD setup timings (not applicable to at91rm9200)
+ * @pulse: NCS/NWE/NRD pulse timings (not applicable to at91rm9200)
+ * @cycle: NWE/NRD cycle timings (not applicable to at91rm9200)
+ * @timings: advanced NAND related timings (only applicable to HSMC)
+ * @mode: all kind of config parameters (see the fields definition above).
+ * The mode fields are different on at91rm9200
+ */
+struct atmel_smc_cs_conf {
+ u32 setup;
+ u32 pulse;
+ u32 cycle;
+ u32 timings;
+ u32 mode;
+};
+
+void atmel_smc_cs_conf_init(struct atmel_smc_cs_conf *conf);
+int atmel_smc_cs_conf_set_timing(struct atmel_smc_cs_conf *conf,
+ unsigned int shift,
+ unsigned int ncycles);
+int atmel_smc_cs_conf_set_setup(struct atmel_smc_cs_conf *conf,
+ unsigned int shift, unsigned int ncycles);
+int atmel_smc_cs_conf_set_pulse(struct atmel_smc_cs_conf *conf,
+ unsigned int shift, unsigned int ncycles);
+int atmel_smc_cs_conf_set_cycle(struct atmel_smc_cs_conf *conf,
+ unsigned int shift, unsigned int ncycles);
+void atmel_smc_cs_conf_apply(struct regmap *regmap, int cs,
+ const struct atmel_smc_cs_conf *conf);
+void atmel_hsmc_cs_conf_apply(struct regmap *regmap,
+ const struct atmel_hsmc_reg_layout *reglayout,
+ int cs, const struct atmel_smc_cs_conf *conf);
+void atmel_smc_cs_conf_get(struct regmap *regmap, int cs,
+ struct atmel_smc_cs_conf *conf);
+void atmel_hsmc_cs_conf_get(struct regmap *regmap,
+ const struct atmel_hsmc_reg_layout *reglayout,
+ int cs, struct atmel_smc_cs_conf *conf);
+const struct atmel_hsmc_reg_layout *
+atmel_hsmc_get_reg_layout(ofnode np);
+
+#endif /* _LINUX_MFD_SYSCON_ATMEL_SMC_H_ */