2014-01-09 12:35:03

by Jean-Jacques Hiblot

[permalink] [raw]
Subject: [PATCH v2 00/12] Device Tree support for the at91sam9261ek

This patch set aims at bringing a basic device tree support for the sam9261.
It's mostly based on the sam9263 stuff.
It introduces a new driver for the smc/ebi bus. It's used to configure the EBI
from the DT. I haven't documented its DT bindings yet. Timings can be provided
as raw values or nanoseconds.

Change since V1:
* changed the DT representation to use address translation and separate the
timings' configuration from the device properties by adding a "simple-bus"
inetrmediate node.
* moved the smc driver from drivers/bus to drivers/memmory
* smc driver now accepts timings in nanoseconds as well as raw register values
* smc driver can clip the timings if they're out of bound and dump them to the
console
* DM9000 timings are now described in nanosecs (for the virtue of example)

supported features:
* dbgu
* nand
* lcd
* ethernet
* leds

Jean-Jacques

Jean-Jacques Hiblot (12):
at91: dt: Add at91sam9261 dt SoC support
at91: dt: sam9261: Basic Device Tree support for the at91sam9261ek
at91: dt: sam9261: Added support for the lcd display
at91: smc: export sam9_smc_cs_read and sam9_smc_cs_configure.
at91: smc: Increased the size of tdf_cycles in struct sam9_smc_config.
at91: smc: Adds helper functions to validate and clip the smc timings.
at91: dt: smc: Added smc bus driver
at91: sam9261: Add a clock definition for the smc
at91: dt: sam9261: Pinmux DT entries for the SMC/EBI interface
at91: dt: sam9261: Add an entry in the DT for the SMC/EBI bus driver.
at91: dt: sam9261: moved the NAND under the smc node
at91: dt: sam9261: Added DM9000 in the device tree

arch/arm/boot/dts/Makefile | 2 +
arch/arm/boot/dts/at91sam9261.dtsi | 639 +++++++++++++++++++++++++
arch/arm/boot/dts/at91sam9261ek.dts | 164 +++++++
arch/arm/mach-at91/at91sam9261.c | 17 +
arch/arm/mach-at91/include/mach/at91sam9_smc.h | 6 +-
arch/arm/mach-at91/sam9_smc.c | 81 +++-
drivers/memory/Kconfig | 10 +
drivers/memory/Makefile | 1 +
drivers/memory/atmel-smc.c | 431 +++++++++++++++++
9 files changed, 1348 insertions(+), 3 deletions(-)
create mode 100644 arch/arm/boot/dts/at91sam9261.dtsi
create mode 100644 arch/arm/boot/dts/at91sam9261ek.dts
create mode 100644 drivers/memory/atmel-smc.c

--
1.8.5.2


2014-01-09 12:35:10

by Jean-Jacques Hiblot

[permalink] [raw]
Subject: [PATCH v2 01/12] at91: dt: Add at91sam9261 dt SoC support

This patch adds the basics to support the Device Tree on a sam9261-based platform

Signed-off-by: Jean-Jacques Hiblot <[email protected]>
---
arch/arm/boot/dts/at91sam9261.dtsi | 476 +++++++++++++++++++++++++++++++++++++
arch/arm/mach-at91/at91sam9261.c | 15 ++
2 files changed, 491 insertions(+)
create mode 100644 arch/arm/boot/dts/at91sam9261.dtsi

diff --git a/arch/arm/boot/dts/at91sam9261.dtsi b/arch/arm/boot/dts/at91sam9261.dtsi
new file mode 100644
index 0000000..773c3d6
--- /dev/null
+++ b/arch/arm/boot/dts/at91sam9261.dtsi
@@ -0,0 +1,476 @@
+/*
+ * at91sam9261.dtsi - Device Tree Include file for AT91SAM9261 SoC
+ *
+ * Copyright (C) 2013 Jean-Jacques Hiblot <[email protected]>
+ *
+ * Licensed under GPLv2 only.
+ */
+
+#include "skeleton.dtsi"
+#include <dt-bindings/pinctrl/at91.h>
+#include <dt-bindings/interrupt-controller/irq.h>
+#include <dt-bindings/gpio/gpio.h>
+
+/ {
+ model = "Atmel AT91SAM9261 family SoC";
+ compatible = "atmel,at91sam9261";
+ interrupt-parent = <&aic>;
+
+ aliases {
+ serial0 = &dbgu;
+ serial1 = &usart0;
+ serial2 = &usart1;
+ serial3 = &usart2;
+ gpio0 = &pioA;
+ gpio1 = &pioB;
+ gpio2 = &pioC;
+ tcb0 = &tcb0;
+ i2c0 = &i2c0;
+ ssc0 = &ssc0;
+ ssc1 = &ssc1;
+ };
+ cpus {
+ #address-cells = <0>;
+ #size-cells = <0>;
+
+ cpu {
+ compatible = "arm,arm926ej-s";
+ device_type = "cpu";
+ };
+ };
+
+ memory {
+ reg = <0x20000000 0x08000000>;
+ };
+
+ ahb {
+ compatible = "simple-bus";
+ #address-cells = <1>;
+ #size-cells = <1>;
+ ranges;
+
+ apb {
+ compatible = "simple-bus";
+ #address-cells = <1>;
+ #size-cells = <1>;
+ ranges;
+
+ aic: interrupt-controller@fffff000 {
+ #interrupt-cells = <3>;
+ compatible = "atmel,at91rm9200-aic";
+ interrupt-controller;
+ reg = <0xfffff000 0x200>;
+ atmel,external-irqs = <29 30 31>;
+ };
+
+ pmc: pmc@fffffc00 {
+ compatible = "atmel,at91rm9200-pmc";
+ reg = <0xfffffc00 0x100>;
+ };
+
+ ramc: ramc@ffffea00 {
+ compatible = "atmel,at91sam9260-sdramc";
+ reg = <0xffffea00 0x200>;
+ };
+
+ pit: timer@fffffd30 {
+ compatible = "atmel,at91sam9260-pit";
+ reg = <0xfffffd30 0xf>;
+ interrupts = <1 IRQ_TYPE_LEVEL_HIGH 7>;
+ };
+
+ tcb0: timer@fffa0000 {
+ compatible = "atmel,at91rm9200-tcb";
+ reg = <0xfffa0000 0x100>;
+ interrupts = < 17 IRQ_TYPE_LEVEL_HIGH 0
+ 18 IRQ_TYPE_LEVEL_HIGH 0
+ 19 IRQ_TYPE_LEVEL_HIGH 0
+ >;
+ status = "disabled";
+ };
+
+ rstc@fffffd00 {
+ compatible = "atmel,at91sam9260-rstc";
+ reg = <0xfffffd00 0x10>;
+ };
+
+ shdwc@fffffd10 {
+ compatible = "atmel,at91sam9260-shdwc";
+ reg = <0xfffffd10 0x10>;
+ };
+
+ pinctrl@fffff400 {
+ #address-cells = <1>;
+ #size-cells = <1>;
+ compatible = "atmel,at91rm9200-pinctrl", "simple-bus";
+ ranges = <0xfffff400 0xfffff400 0xa00>;
+
+ atmel,mux-mask = <
+ /* A B */
+ 0xffffffff 0xfffffff7 /* pioA */
+ 0xffffffff 0xfffffff4 /* pioB */
+ 0xffffffff 0xffffff07 /* pioC */
+ >;
+
+ /* shared pinctrl settings */
+ dbgu {
+ pinctrl_dbgu: dbgu-0 {
+ atmel,pins =
+ <AT91_PIOA 9 AT91_PERIPH_A AT91_PINCTRL_NONE /* PA9 periph A */
+ AT91_PIOA 10 AT91_PERIPH_A AT91_PINCTRL_PULL_UP>; /* PA10 periph A with pullup */
+ };
+ };
+
+ usart0 {
+ pinctrl_usart0: usart0-0 {
+ atmel,pins =
+ <AT91_PIOC 8 AT91_PERIPH_A AT91_PINCTRL_PULL_UP /* PC8 periph A with pullup */
+ AT91_PIOC 9 AT91_PERIPH_A AT91_PINCTRL_NONE>; /* PC9 periph A */
+ };
+
+ pinctrl_usart0_rts: usart0_rts-0 {
+ atmel,pins =
+ <AT91_PIOC 10 AT91_PERIPH_A AT91_PINCTRL_NONE>; /* PC10 periph A */
+ };
+
+ pinctrl_usart0_cts: usart0_cts-0 {
+ atmel,pins =
+ <AT91_PIOC 11 AT91_PERIPH_A AT91_PINCTRL_NONE>; /* PC11 periph A */
+ };
+ };
+
+ usart1 {
+ pinctrl_usart1: usart1-0 {
+ atmel,pins =
+ <AT91_PIOC 12 AT91_PERIPH_A AT91_PINCTRL_PULL_UP /* PC12 periph A with pullup */
+ AT91_PIOC 13 AT91_PERIPH_A AT91_PINCTRL_NONE>; /* PC13 periph A */
+ };
+
+ pinctrl_usart1_rts: usart1_rts-0 {
+ atmel,pins =
+ <AT91_PIOA 12 AT91_PERIPH_B AT91_PINCTRL_NONE>; /* PA12 periph B */
+ };
+
+ pinctrl_usart1_cts: usart1_cts-0 {
+ atmel,pins =
+ <AT91_PIOA 13 AT91_PERIPH_B AT91_PINCTRL_NONE>; /* PA13 periph B */
+ };
+ };
+
+ usart2 {
+ pinctrl_usart2: usart2-0 {
+ atmel,pins =
+ <AT91_PIOC 14 AT91_PERIPH_A AT91_PINCTRL_PULL_UP /* PC14 periph A with pullup */
+ AT91_PIOC 15 AT91_PERIPH_A AT91_PINCTRL_NONE>; /* PC15 periph A */
+ };
+
+ pinctrl_usart2_rts: usart2_rts-0 {
+ atmel,pins =
+ <AT91_PIOA 15 AT91_PERIPH_B AT91_PINCTRL_NONE>; /* PA15 periph B */
+ };
+
+ pinctrl_usart2_cts: usart2_cts-0 {
+ atmel,pins =
+ <AT91_PIOA 16 AT91_PERIPH_B AT91_PINCTRL_NONE>; /* PA16 periph B */
+ };
+ };
+
+ nand {
+ pinctrl_nand: nand-0 {
+ atmel,pins =
+ <AT91_PIOC 15 AT91_PERIPH_GPIO AT91_PINCTRL_PULL_UP /* PC15 gpio RDY pin pull_up*/
+ AT91_PIOC 14 AT91_PERIPH_GPIO AT91_PINCTRL_PULL_UP>; /* PC14 gpio enable pin pull_up */
+ };
+ };
+
+ mmc0 {
+ pinctrl_mmc0_clk: mmc0_clk-0 {
+ atmel,pins =
+ <AT91_PIOA 2 AT91_PERIPH_B AT91_PINCTRL_NONE>; /* PA2 periph B */
+ };
+
+ pinctrl_mmc0_slot0_cmd_dat0: mmc0_slot0_cmd_dat0-0 {
+ atmel,pins =
+ <AT91_PIOA 1 AT91_PERIPH_B AT91_PINCTRL_PULL_UP /* PA1 periph B with pullup */
+ AT91_PIOA 0 AT91_PERIPH_B AT91_PINCTRL_PULL_UP>; /* PA0 periph B with pullup */
+ };
+
+ pinctrl_mmc0_slot0_dat1_3: mmc0_slot0_dat1_3-0 {
+ atmel,pins =
+ <AT91_PIOA 4 AT91_PERIPH_B AT91_PINCTRL_PULL_UP /* PA4 periph B with pullup */
+ AT91_PIOA 5 AT91_PERIPH_B AT91_PINCTRL_PULL_UP /* PA5 periph B with pullup */
+ AT91_PIOA 6 AT91_PERIPH_B AT91_PINCTRL_PULL_UP>; /* PA6 periph B with pullup */
+ };
+ };
+
+ ssc0 {
+ pinctrl_ssc0_tx: ssc0_tx-0 {
+ atmel,pins =
+ <AT91_PIOB 21 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB21 periph A */
+ AT91_PIOB 22 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB22 periph A */
+ AT91_PIOB 23 AT91_PERIPH_A AT91_PINCTRL_NONE>; /* PB23 periph A */
+ };
+
+ pinctrl_ssc0_rx: ssc0_rx-0 {
+ atmel,pins =
+ <AT91_PIOB 24 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB24 periph A */
+ AT91_PIOB 25 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB25 periph A */
+ AT91_PIOB 26 AT91_PERIPH_A AT91_PINCTRL_NONE>; /* PB26 periph A */
+ };
+ };
+
+ ssc1 {
+ pinctrl_ssc1_tx: ssc1_tx-0 {
+ atmel,pins =
+ <AT91_PIOA 17 AT91_PERIPH_B AT91_PINCTRL_NONE /* PA17 periph B */
+ AT91_PIOA 18 AT91_PERIPH_B AT91_PINCTRL_NONE /* PA18 periph B */
+ AT91_PIOA 19 AT91_PERIPH_B AT91_PINCTRL_NONE>; /* PA19 periph B */
+ };
+
+ pinctrl_ssc1_rx: ssc1_rx-0 {
+ atmel,pins =
+ <AT91_PIOA 20 AT91_PERIPH_B AT91_PINCTRL_NONE /* PA20 periph B */
+ AT91_PIOA 21 AT91_PERIPH_B AT91_PINCTRL_NONE /* PA21 periph B */
+ AT91_PIOA 22 AT91_PERIPH_B AT91_PINCTRL_NONE>; /* PA22 periph B */
+ };
+ };
+
+ spi0 {
+ pinctrl_spi0: spi0-0 {
+ atmel,pins =
+ <AT91_PIOA 0 AT91_PERIPH_A AT91_PINCTRL_NONE /* PA0 periph A SPI0_MISO pin */
+ AT91_PIOA 1 AT91_PERIPH_A AT91_PINCTRL_NONE /* PA1 periph A SPI0_MOSI pin */
+ AT91_PIOA 2 AT91_PERIPH_A AT91_PINCTRL_NONE>; /* PA2 periph A SPI0_SPCK pin */
+ };
+ };
+
+ spi1 {
+ pinctrl_spi1: spi1-0 {
+ atmel,pins =
+ <AT91_PIOB 30 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB30 periph A SPI1_MISO pin */
+ AT91_PIOB 31 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB31 periph A SPI1_MOSI pin */
+ AT91_PIOB 29 AT91_PERIPH_A AT91_PINCTRL_NONE>; /* PB29 periph A SPI1_SPCK pin */
+ };
+ };
+
+ tcb0 {
+ pinctrl_tcb0_tclk0: tcb0_tclk0-0 {
+ atmel,pins = <AT91_PIOC 16 AT91_PERIPH_B AT91_PINCTRL_NONE>;
+ };
+
+ pinctrl_tcb0_tclk1: tcb0_tclk1-0 {
+ atmel,pins = <AT91_PIOC 17 AT91_PERIPH_B AT91_PINCTRL_NONE>;
+ };
+
+ pinctrl_tcb0_tclk2: tcb0_tclk2-0 {
+ atmel,pins = <AT91_PIOC 18 AT91_PERIPH_B AT91_PINCTRL_NONE>;
+ };
+
+ pinctrl_tcb0_tioa0: tcb0_tioa0-0 {
+ atmel,pins = <AT91_PIOC 19 AT91_PERIPH_B AT91_PINCTRL_NONE>;
+ };
+
+ pinctrl_tcb0_tioa1: tcb0_tioa1-0 {
+ atmel,pins = <AT91_PIOC 21 AT91_PERIPH_B AT91_PINCTRL_NONE>;
+ };
+
+ pinctrl_tcb0_tioa2: tcb0_tioa2-0 {
+ atmel,pins = <AT91_PIOC 23 AT91_PERIPH_B AT91_PINCTRL_NONE>;
+ };
+
+ pinctrl_tcb0_tiob0: tcb0_tiob0-0 {
+ atmel,pins = <AT91_PIOC 20 AT91_PERIPH_B AT91_PINCTRL_NONE>;
+ };
+
+ pinctrl_tcb0_tiob1: tcb0_tiob1-0 {
+ atmel,pins = <AT91_PIOC 22 AT91_PERIPH_B AT91_PINCTRL_NONE>;
+ };
+
+ pinctrl_tcb0_tiob2: tcb0_tiob2-0 {
+ atmel,pins = <AT91_PIOC 24 AT91_PERIPH_B AT91_PINCTRL_NONE>;
+ };
+ };
+
+ pioA: gpio@fffff400 {
+ compatible = "atmel,at91rm9200-gpio";
+ reg = <0xfffff400 0x200>;
+ interrupts = <2 IRQ_TYPE_LEVEL_HIGH 1>;
+ #gpio-cells = <2>;
+ gpio-controller;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ };
+
+ pioB: gpio@fffff600 {
+ compatible = "atmel,at91rm9200-gpio";
+ reg = <0xfffff600 0x200>;
+ interrupts = <3 IRQ_TYPE_LEVEL_HIGH 1>;
+ #gpio-cells = <2>;
+ gpio-controller;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ };
+
+ pioC: gpio@fffff800 {
+ compatible = "atmel,at91rm9200-gpio";
+ reg = <0xfffff800 0x200>;
+ interrupts = <4 IRQ_TYPE_LEVEL_HIGH 1>;
+ #gpio-cells = <2>;
+ gpio-controller;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ };
+ };
+
+ dbgu: serial@fffff200 {
+ compatible = "atmel,at91sam9260-usart";
+ reg = <0xfffff200 0x200>;
+ interrupts = <1 IRQ_TYPE_LEVEL_HIGH 7>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_dbgu>;
+ status = "disabled";
+ };
+
+ usart0: serial@fffb0000 {
+ compatible = "atmel,at91sam9260-usart";
+ reg = <0xfffb0000 0x200>;
+ interrupts = <6 IRQ_TYPE_LEVEL_HIGH 5>;
+ atmel,use-dma-rx;
+ atmel,use-dma-tx;
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_usart0>;
+ status = "disabled";
+ };
+
+ usart1: serial@ffffb400 {
+ compatible = "atmel,at91sam9260-usart";
+ reg = <0xfffb4000 0x200>;
+ interrupts = <7 IRQ_TYPE_LEVEL_HIGH 5>;
+ atmel,use-dma-rx;
+ atmel,use-dma-tx;
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_usart1>;
+ status = "disabled";
+ };
+
+ usart2: serial@fff94000 {
+ compatible = "atmel,at91sam9260-usart";
+ reg = <0xfffb8000 0x200>;
+ interrupts = <8 IRQ_TYPE_LEVEL_HIGH 5>;
+ atmel,use-dma-rx;
+ atmel,use-dma-tx;
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_usart2>;
+ status = "disabled";
+ };
+
+ ssc0: ssc@fffbc000 {
+ compatible = "atmel,at91rm9200-ssc";
+ reg = <0xfffbc000 0x4000>;
+ interrupts = <14 IRQ_TYPE_LEVEL_HIGH 5>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_ssc0_tx &pinctrl_ssc0_rx>;
+ status = "disabled";
+ };
+
+ ssc1: ssc@fffc0000 {
+ compatible = "atmel,at91rm9200-ssc";
+ reg = <0xfffc0000 0x4000>;
+ interrupts = <15 IRQ_TYPE_LEVEL_HIGH 5>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_ssc1_tx &pinctrl_ssc1_rx>;
+ status = "disabled";
+ };
+
+ usb1: gadget@fffa4000 {
+ compatible = "atmel,at91rm9200-udc";
+ reg = <0xfffa4000 0x4000>;
+ interrupts = <10 IRQ_TYPE_LEVEL_HIGH 2>;
+ status = "disabled";
+ };
+
+ i2c0: i2c@fffac000 {
+ compatible = "atmel,at91sam9261-i2c";
+ reg = <0xfffac000 0x100>;
+ interrupts = <11 IRQ_TYPE_LEVEL_HIGH 6>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ mmc0: mmc@fffa8000 {
+ compatible = "atmel,hsmci";
+ reg = <0xfffa8000 0x600>;
+ interrupts = <9 IRQ_TYPE_LEVEL_HIGH 0>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ watchdog@fffffd40 {
+ compatible = "atmel,at91sam9260-wdt";
+ reg = <0xfffffd40 0x10>;
+ status = "disabled";
+ };
+
+ spi0: spi@fffc8000 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ compatible = "atmel,at91rm9200-spi";
+ reg = <0xfffc8000 0x200>;
+ interrupts = <12 IRQ_TYPE_LEVEL_HIGH 3>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_spi0>;
+ status = "disabled";
+ };
+
+ spi1: spi@fffcc000 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ compatible = "atmel,at91rm9200-spi";
+ reg = <0xfffcc000 0x200>;
+ interrupts = <13 IRQ_TYPE_LEVEL_HIGH 3>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_spi1>;
+ status = "disabled";
+ };
+ };
+
+ nand0: nand@40000000 {
+ compatible = "atmel,at91rm9200-nand";
+ #address-cells = <1>;
+ #size-cells = <1>;
+ reg = <0x40000000 0x10000000>;
+ atmel,nand-addr-offset = <22>;
+ atmel,nand-cmd-offset = <21>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_nand>;
+
+ gpios = <&pioC 15 GPIO_ACTIVE_HIGH
+ &pioC 14 GPIO_ACTIVE_HIGH
+ 0
+ >;
+ status = "disabled";
+ };
+
+ usb0: ohci@00500000 {
+ compatible = "atmel,at91rm9200-ohci", "usb-ohci";
+ reg = <0x00500000 0x100000>;
+ interrupts = <20 IRQ_TYPE_LEVEL_HIGH 2>;
+ status = "disabled";
+ };
+ };
+
+ i2c@0 {
+ compatible = "i2c-gpio";
+ gpios = <&pioA 7 GPIO_ACTIVE_HIGH /* sda */
+ &pioA 8 GPIO_ACTIVE_HIGH /* scl */
+ >;
+ i2c-gpio,sda-open-drain;
+ i2c-gpio,scl-open-drain;
+ i2c-gpio,delay-us = <2>; /* ~100 kHz */
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+};
diff --git a/arch/arm/mach-at91/at91sam9261.c b/arch/arm/mach-at91/at91sam9261.c
index 6276b4c..200d17a 100644
--- a/arch/arm/mach-at91/at91sam9261.c
+++ b/arch/arm/mach-at91/at91sam9261.c
@@ -189,6 +189,21 @@ static struct clk_lookup periph_clocks_lookups[] = {
CLKDEV_CON_ID("pioA", &pioA_clk),
CLKDEV_CON_ID("pioB", &pioB_clk),
CLKDEV_CON_ID("pioC", &pioC_clk),
+ /* more usart lookup table for DT entries */
+ CLKDEV_CON_DEV_ID("usart", "fffff200.serial", &mck),
+ CLKDEV_CON_DEV_ID("usart", "fffb0000.serial", &usart0_clk),
+ CLKDEV_CON_DEV_ID("usart", "ffffb400.serial", &usart1_clk),
+ CLKDEV_CON_DEV_ID("usart", "fff94000.serial", &usart2_clk),
+ /* more tc lookup table for DT entries */
+ CLKDEV_CON_DEV_ID("t0_clk", "fffa0000.timer", &tc0_clk),
+ CLKDEV_CON_DEV_ID("hclk", "500000.ohci", &ohci_clk),
+ CLKDEV_CON_DEV_ID("spi_clk", "fffc8000.spi", &spi0_clk),
+ CLKDEV_CON_DEV_ID("spi_clk", "fffcc000.spi", &spi1_clk),
+ CLKDEV_CON_DEV_ID("mci_clk", "fffa8000.mmc", &mmc_clk),
+ CLKDEV_CON_DEV_ID(NULL, "fffac000.i2c", &twi_clk),
+ CLKDEV_CON_DEV_ID(NULL, "fffff400.gpio", &pioA_clk),
+ CLKDEV_CON_DEV_ID(NULL, "fffff600.gpio", &pioB_clk),
+ CLKDEV_CON_DEV_ID(NULL, "fffff800.gpio", &pioC_clk),
};

static struct clk_lookup usart_clocks_lookups[] = {
--
1.8.5.2

2014-01-09 12:35:24

by Jean-Jacques Hiblot

[permalink] [raw]
Subject: [PATCH v2 02/12] at91: dt: sam9261: Basic Device Tree support for the at91sam9261ek

This patch implements a simple DTS to boot a at91sam9261ek with a dt-enabled
kernel (at91_dt_defconfig).
Only dbgu, nand and watchdog are described in the DT.

Signed-off-by: Jean-Jacques Hiblot <[email protected]>
---
arch/arm/boot/dts/Makefile | 2 +
arch/arm/boot/dts/at91sam9261ek.dts | 75 +++++++++++++++++++++++++++++++++++++
2 files changed, 77 insertions(+)
create mode 100644 arch/arm/boot/dts/at91sam9261ek.dts

diff --git a/arch/arm/boot/dts/Makefile b/arch/arm/boot/dts/Makefile
index 772a30e..ece523d 100644
--- a/arch/arm/boot/dts/Makefile
+++ b/arch/arm/boot/dts/Makefile
@@ -11,6 +11,8 @@ dtb-$(CONFIG_ARCH_AT91) += ethernut5.dtb
dtb-$(CONFIG_ARCH_AT91) += evk-pro3.dtb
dtb-$(CONFIG_ARCH_AT91) += tny_a9260.dtb
dtb-$(CONFIG_ARCH_AT91) += usb_a9260.dtb
+# sam9261
+dtb-$(CONFIG_ARCH_AT91) += at91sam9261ek.dtb
# sam9263
dtb-$(CONFIG_ARCH_AT91) += at91sam9263ek.dtb
dtb-$(CONFIG_ARCH_AT91) += tny_a9263.dtb
diff --git a/arch/arm/boot/dts/at91sam9261ek.dts b/arch/arm/boot/dts/at91sam9261ek.dts
new file mode 100644
index 0000000..f3d22a9
--- /dev/null
+++ b/arch/arm/boot/dts/at91sam9261ek.dts
@@ -0,0 +1,75 @@
+/*
+ * at91sam9261ek.dts - Device Tree file for Atmel at91sam9261 reference board
+ *
+ * Copyright (C) 2013 Jean-Jacques Hiblot <[email protected]>
+ *
+ * Licensed under GPLv2 only.
+ */
+/dts-v1/;
+#include "at91sam9261.dtsi"
+
+/ {
+ model = "Atmel at91sam9261ek";
+ compatible = "atmel,at91sam9261ek", "atmel,at91sam9261", "atmel,at91sam9";
+
+ chosen {
+ bootargs = "mem=64M console=ttyS0,115200";
+ };
+
+ memory {
+ reg = <0x20000000 0x4000000>;
+ };
+
+ clocks {
+ #address-cells = <1>;
+ #size-cells = <1>;
+ ranges;
+
+ main_clock: clock@0 {
+ compatible = "atmel,osc", "fixed-clock";
+ clock-frequency = <18432000>;
+ };
+ };
+
+ ahb {
+ apb {
+ dbgu: serial@fffff200 {
+ status = "okay";
+ };
+
+ watchdog@fffffd40 {
+ status = "okay";
+ };
+ };
+
+ nand0: nand@40000000 {
+ nand-bus-width = <8>;
+ nand-ecc-mode = "soft";
+ nand-on-flash-bbt = <1>;
+ status = "okay";
+ at91bootstrap@0 {
+ label = "at91bootstrap";
+ reg = <0x0 0x20000>;
+ };
+ };
+ };
+
+ leds {
+ compatible = "gpio-leds";
+ ds8 {
+ label = "ds8";
+ gpios = <&pioA 13 GPIO_ACTIVE_LOW>;
+ linux,default-trigger = "none";
+ };
+ ds7 {
+ label = "ds7";
+ gpios = <&pioA 14 GPIO_ACTIVE_LOW>;
+ linux,default-trigger = "nand-disk";
+ };
+ ds1 {
+ label = "ds1";
+ gpios = <&pioA 23 GPIO_ACTIVE_LOW>;
+ linux,default-trigger = "heartbeat";
+ };
+ };
+};
--
1.8.5.2

2014-01-09 12:35:32

by Jean-Jacques Hiblot

[permalink] [raw]
Subject: [PATCH v2 03/12] at91: dt: sam9261: Added support for the lcd display

Signed-off-by: Jean-Jacques Hiblot <[email protected]>
---
arch/arm/boot/dts/at91sam9261.dtsi | 37 ++++++++++++++++++++++++++++++++++++-
arch/arm/boot/dts/at91sam9261ek.dts | 31 +++++++++++++++++++++++++++++++
arch/arm/mach-at91/at91sam9261.c | 1 +
3 files changed, 68 insertions(+), 1 deletion(-)

diff --git a/arch/arm/boot/dts/at91sam9261.dtsi b/arch/arm/boot/dts/at91sam9261.dtsi
index 773c3d6..cd219b9 100644
--- a/arch/arm/boot/dts/at91sam9261.dtsi
+++ b/arch/arm/boot/dts/at91sam9261.dtsi
@@ -290,7 +290,33 @@
atmel,pins = <AT91_PIOC 24 AT91_PERIPH_B AT91_PINCTRL_NONE>;
};
};
-
+ fb {
+ pinctrl_fb: fb-0 {
+ atmel,pins =
+ <AT91_PIOB 1 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB1 periph A */
+ AT91_PIOB 2 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB2 periph A */
+ AT91_PIOB 3 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB3 periph A */
+ AT91_PIOB 7 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB7 periph A */
+ AT91_PIOB 8 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB8 periph A */
+ AT91_PIOB 9 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB9 periph A */
+ AT91_PIOB 10 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB10 periph A */
+ AT91_PIOB 11 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB11 periph A */
+ AT91_PIOB 12 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB12 periph A */
+ AT91_PIOB 15 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB15 periph A */
+ AT91_PIOB 16 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB16 periph A */
+ AT91_PIOB 17 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB17 periph A */
+ AT91_PIOB 18 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB18 periph A */
+ AT91_PIOB 19 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB19 periph A */
+ AT91_PIOB 20 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB20 periph A */
+ AT91_PIOB 23 AT91_PERIPH_B AT91_PINCTRL_NONE /* PB23 periph B */
+ AT91_PIOB 24 AT91_PERIPH_B AT91_PINCTRL_NONE /* PB24 periph B */
+ AT91_PIOB 25 AT91_PERIPH_B AT91_PINCTRL_NONE /* PB25 periph B */
+ AT91_PIOB 26 AT91_PERIPH_B AT91_PINCTRL_NONE /* PB26 periph B */
+ AT91_PIOB 27 AT91_PERIPH_B AT91_PINCTRL_NONE /* PB27 periph B */
+ AT91_PIOB 28 AT91_PERIPH_B AT91_PINCTRL_NONE /* PB28 periph B */
+ >;
+ };
+ };
pioA: gpio@fffff400 {
compatible = "atmel,at91rm9200-gpio";
reg = <0xfffff400 0x200>;
@@ -436,6 +462,15 @@
};
};

+ fb0: fb@0x00600000 {
+ compatible = "atmel,at91sam9261-lcdc";
+ reg = <0x00600000 0x1000>;
+ interrupts = <21 IRQ_TYPE_LEVEL_HIGH 3>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_fb>;
+ status = "disabled";
+ };
+
nand0: nand@40000000 {
compatible = "atmel,at91rm9200-nand";
#address-cells = <1>;
diff --git a/arch/arm/boot/dts/at91sam9261ek.dts b/arch/arm/boot/dts/at91sam9261ek.dts
index f3d22a9..03c05fc 100644
--- a/arch/arm/boot/dts/at91sam9261ek.dts
+++ b/arch/arm/boot/dts/at91sam9261ek.dts
@@ -52,6 +52,37 @@
reg = <0x0 0x20000>;
};
};
+
+ fb0: fb@0x00600000 {
+ display = <&display0>;
+ status = "okay";
+ atmel,power-control-gpio = <&pioA 12 GPIO_ACTIVE_LOW>;
+ display0: display {
+ bits-per-pixel = <16>;
+ atmel,lcdcon-backlight;
+ atmel,dmacon = <0x1>;
+ atmel,lcdcon2 = <0x80008002>;
+ atmel,guard-time = <1>;
+ atmel,lcd-wiring-mode = "BRG";
+
+ display-timings {
+ native-mode = <&timing0>;
+ timing0: timing0 {
+ clock-frequency = <4965000>;
+ hactive = <240>;
+ vactive = <320>;
+ hback-porch = <1>;
+ hfront-porch = <33>;
+ vback-porch = <1>;
+ vfront-porch = <0>;
+ hsync-len = <5>;
+ vsync-len = <1>;
+ hsync-active = <1>;
+ vsync-active = <1>;
+ };
+ };
+ };
+ };
};

leds {
diff --git a/arch/arm/mach-at91/at91sam9261.c b/arch/arm/mach-at91/at91sam9261.c
index 200d17a..a67bfe6 100644
--- a/arch/arm/mach-at91/at91sam9261.c
+++ b/arch/arm/mach-at91/at91sam9261.c
@@ -197,6 +197,7 @@ static struct clk_lookup periph_clocks_lookups[] = {
/* more tc lookup table for DT entries */
CLKDEV_CON_DEV_ID("t0_clk", "fffa0000.timer", &tc0_clk),
CLKDEV_CON_DEV_ID("hclk", "500000.ohci", &ohci_clk),
+ CLKDEV_CON_DEV_ID("hclk", "600000.fb", &hck1),
CLKDEV_CON_DEV_ID("spi_clk", "fffc8000.spi", &spi0_clk),
CLKDEV_CON_DEV_ID("spi_clk", "fffcc000.spi", &spi1_clk),
CLKDEV_CON_DEV_ID("mci_clk", "fffa8000.mmc", &mmc_clk),
--
1.8.5.2

2014-01-09 12:35:40

by Jean-Jacques Hiblot

[permalink] [raw]
Subject: [PATCH v2 07/12] at91: dt: smc: Added smc bus driver

The EBI/SMC external interface is used to access external peripherals (NAND
and Ethernet controller in the case of sam9261ek). Different configurations and
timings are required for those peripherals. This bus driver can be used to
setup the bus timings/configuration from the device tree.
It currently accepts timings in clock period and in nanoseconds.

Signed-off-by: Jean-Jacques Hiblot <[email protected]>
---
drivers/memory/Kconfig | 10 ++
drivers/memory/Makefile | 1 +
drivers/memory/atmel-smc.c | 431 +++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 442 insertions(+)
create mode 100644 drivers/memory/atmel-smc.c

diff --git a/drivers/memory/Kconfig b/drivers/memory/Kconfig
index 29a11db..fbdfd63 100644
--- a/drivers/memory/Kconfig
+++ b/drivers/memory/Kconfig
@@ -50,4 +50,14 @@ config TEGRA30_MC
analysis, especially for IOMMU/SMMU(System Memory Management
Unit) module.

+config ATMEL_SMC
+ bool "Atmel SMC/EBI driver"
+ default y
+ depends on SOC_AT91SAM9 && OF
+ help
+ Driver for Atmel SMC/EBI controller.
+ Used to configure the EBI (external bus interface) when the device-
+ tree is used. This bus supports NANDs, external ethernet controller,
+ SRAMs, ATA devices, etc.
+
endif
diff --git a/drivers/memory/Makefile b/drivers/memory/Makefile
index 969d923..101abc4 100644
--- a/drivers/memory/Makefile
+++ b/drivers/memory/Makefile
@@ -9,3 +9,4 @@ obj-$(CONFIG_TI_EMIF) += emif.o
obj-$(CONFIG_MVEBU_DEVBUS) += mvebu-devbus.o
obj-$(CONFIG_TEGRA20_MC) += tegra20-mc.o
obj-$(CONFIG_TEGRA30_MC) += tegra30-mc.o
+obj-$(CONFIG_ATMEL_SMC) += atmel-smc.o
diff --git a/drivers/memory/atmel-smc.c b/drivers/memory/atmel-smc.c
new file mode 100644
index 0000000..0a1d9ba
--- /dev/null
+++ b/drivers/memory/atmel-smc.c
@@ -0,0 +1,431 @@
+/*
+ * EBI driver for Atmel SAM9 chips
+ * inspired by the fsl weim bus driver
+ *
+ * Copyright (C) 2013 JJ Hiblot.
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+#include <linux/module.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/of_device.h>
+#include <mach/at91sam9_smc.h>
+
+struct smc_data {
+ struct clk *bus_clk;
+ void __iomem *base;
+ struct device *dev;
+};
+
+struct at91_smc_devtype {
+ unsigned int cs_count;
+};
+
+static const struct at91_smc_devtype sam9261_smc_devtype = {
+ .cs_count = 6,
+};
+
+static const struct of_device_id smc_id_table[] = {
+ { .compatible = "atmel,at91sam9261-smc", .data = &sam9261_smc_devtype},
+ { }
+};
+MODULE_DEVICE_TABLE(of, smc_id_table);
+
+struct smc_parameters_type {
+ const char *name;
+ u16 width;
+ u16 shift;
+};
+
+static const struct smc_parameters_type smc_parameters[] = {
+ {"smc,burst_size", 2, 28},
+ {"smc,burst_enabled", 1, 24},
+ {"smc,tdf_mode", 1, 20},
+ {"smc,bus_width", 2, 12},
+ {"smc,byte_access_type", 1, 8},
+ {"smc,nwait_mode", 2, 4},
+ {"smc,write_mode", 1, 0},
+ {"smc,read_mode", 1, 1},
+ {NULL}
+};
+
+static int get_mode_register_from_dt(struct smc_data *smc,
+ struct device_node *np,
+ struct sam9_smc_config *cfg)
+{
+ int ret;
+ u32 val;
+ struct device *dev = smc->dev;
+ const struct smc_parameters_type *p = smc_parameters;
+ u32 mode = cfg->mode;
+
+ while (p->name) {
+ ret = of_property_read_u32(np, p->name , &val);
+ if (ret == -EINVAL) {
+ dev_dbg(dev, "%s: property %s not set.\n", np->name,
+ p->name);
+ p++;
+ continue;
+ } else if (ret) {
+ dev_err(dev, "%s: can't get property %s.\n", np->name,
+ p->name);
+ return ret;
+ }
+ if (val >= (1<<p->width)) {
+ dev_err(dev, "%s: property %s out of range.\n",
+ np->name, p->name);
+ return -ERANGE;
+ }
+ mode &= ~(((1<<p->width)-1) << p->shift);
+ mode |= (val << p->shift);
+ p++;
+ }
+ cfg->mode = mode;
+ return 0;
+}
+
+static int generic_timing_from_dt(struct smc_data *smc, struct device_node *np,
+ struct sam9_smc_config *cfg)
+{
+ u32 val;
+
+ if (!of_property_read_u32(np, "smc,ncs_read_setup" , &val))
+ cfg->ncs_read_setup = val;
+
+ if (!of_property_read_u32(np, "smc,nrd_setup" , &val))
+ cfg->nrd_setup = val;
+
+ if (!of_property_read_u32(np, "smc,ncs_write_setup" , &val))
+ cfg->ncs_write_setup = val;
+
+ if (!of_property_read_u32(np, "smc,nwe_setup" , &val))
+ cfg->nwe_setup = val;
+
+ if (!of_property_read_u32(np, "smc,ncs_read_pulse" , &val))
+ cfg->ncs_read_pulse = val;
+
+ if (!of_property_read_u32(np, "smc,nrd_pulse" , &val))
+ cfg->nrd_pulse = val;
+
+ if (!of_property_read_u32(np, "smc,ncs_write_pulse" , &val))
+ cfg->ncs_write_pulse = val;
+
+ if (!of_property_read_u32(np, "smc,nwe_pulse" , &val))
+ cfg->nwe_pulse = val;
+
+ if (!of_property_read_u32(np, "smc,read_cycle" , &val))
+ cfg->read_cycle = val;
+
+ if (!of_property_read_u32(np, "smc,write_cycle" , &val))
+ cfg->write_cycle = val;
+
+ if (!of_property_read_u32(np, "smc,tdf_cycles" , &val))
+ cfg->tdf_cycles = val;
+
+ return get_mode_register_from_dt(smc, np, cfg);
+}
+
+/* convert the time in ns in a number of clock cycles */
+static u32 ns_to_cycles(u32 ns, u32 clk)
+{
+ /*
+ * convert the clk to kHz for the rest of the calculation to avoid
+ * overflow
+ */
+ u32 clk_kHz = clk / 1000;
+ u32 ncycles = ((ns * clk_kHz) + 1000000 - 1) / 1000000;
+ return ncycles;
+}
+
+static u32 cycles_to_coded_cycle(u32 cycles, int a, int b)
+{
+ u32 mask_high = (1 << a) - 1;
+ u32 mask_low = (1 << b) - 1;
+ u32 coded;
+
+ /* check if the value can be described with the coded format */
+ if (cycles & (mask_high & ~mask_low)) {
+ /* not representable. we need to round up */
+ cycles |= mask_high;
+ cycles += 1;
+ }
+ /* Now the value can be represented in the coded format */
+ coded = (cycles & ~mask_high) >> (a - b);
+ coded |= (cycles & mask_low);
+ return coded;
+}
+
+static u32 ns_to_rw_cycles(u32 ns, u32 clk)
+{
+ return cycles_to_coded_cycle(ns_to_cycles(ns, clk), 8, 7);
+}
+
+static u32 ns_to_pulse_cycles(u32 ns, u32 clk)
+{
+ return cycles_to_coded_cycle(ns_to_cycles(ns, clk), 8, 6);
+}
+
+static u32 ns_to_setup_cycles(u32 ns, u32 clk)
+{
+ return cycles_to_coded_cycle(ns_to_cycles(ns, clk), 7, 5);
+}
+
+static u32 cycles_to_ns(u32 cycles, u32 clk)
+{
+ /*
+ * convert the clk to kHz for the rest of the calculation to avoid
+ * overflow
+ */
+ u32 clk_kHz = clk / 1000;
+ return (cycles * 1000000) / clk_kHz;
+}
+
+static u32 coded_cycle_to_cycles(u32 coded, int a, int b)
+{
+ u32 cycles = (coded >> b) << a;
+ u32 mask_low = (1 << b) - 1;
+ cycles |= (coded & mask_low);
+ return cycles;
+}
+
+static u32 rw_cycles_to_ns(u32 reg, u32 clk)
+{
+ return cycles_to_ns(coded_cycle_to_cycles(reg, 8, 7), clk);
+}
+
+static u32 pulse_cycles_to_ns(u32 reg, u32 clk)
+{
+ return cycles_to_ns(coded_cycle_to_cycles(reg, 8, 6), clk);
+}
+
+static u32 setup_cycles_to_ns(u32 reg, u32 clk)
+{
+ return cycles_to_ns(coded_cycle_to_cycles(reg, 7, 5), clk);
+}
+
+static void dump_timing(struct smc_data *smc, struct sam9_smc_config *config)
+{
+ u32 freq = clk_get_rate(smc->bus_clk);
+ struct device *dev = smc->dev;
+
+#define DUMP(fn, y) dev_info(dev, "%s : 0x%x (%d ns)\n", #y, config->y,\
+ fn(config->y, freq))
+#define DUMP_PULSE(y) DUMP(pulse_cycles_to_ns, y)
+#define DUMP_RWCYCLE(y) DUMP(rw_cycles_to_ns, y)
+#define DUMP_SETUP(y) DUMP(setup_cycles_to_ns, y)
+#define DUMP_SIMPLE(y) DUMP(cycles_to_ns, y)
+
+ DUMP_SETUP(nwe_setup);
+ DUMP_SETUP(ncs_write_setup);
+ DUMP_SETUP(nrd_setup);
+ DUMP_SETUP(ncs_read_setup);
+ DUMP_PULSE(nwe_pulse);
+ DUMP_PULSE(ncs_write_pulse);
+ DUMP_PULSE(nrd_pulse);
+ DUMP_PULSE(ncs_read_pulse);
+ DUMP_RWCYCLE(write_cycle);
+ DUMP_RWCYCLE(read_cycle);
+ DUMP_SIMPLE(tdf_cycles);
+}
+
+static int ns_timing_from_dt(struct smc_data *smc, struct device_node *np,
+ struct sam9_smc_config *cfg)
+{
+ u32 t_ns;
+ u32 freq = clk_get_rate(smc->bus_clk);
+
+ if (!of_property_read_u32(np, "smc,ncs_read_setup" , &t_ns))
+ cfg->ncs_read_setup = ns_to_setup_cycles(t_ns, freq);
+
+ if (!of_property_read_u32(np, "smc,nrd_setup" , &t_ns))
+ cfg->nrd_setup = ns_to_setup_cycles(t_ns, freq);
+
+ if (!of_property_read_u32(np, "smc,ncs_write_setup" , &t_ns))
+ cfg->ncs_write_setup = ns_to_setup_cycles(t_ns, freq);
+
+ if (!of_property_read_u32(np, "smc,nwe_setup" , &t_ns))
+ cfg->nwe_setup = ns_to_setup_cycles(t_ns, freq);
+
+ if (!of_property_read_u32(np, "smc,ncs_read_pulse" , &t_ns))
+ cfg->ncs_read_pulse = ns_to_pulse_cycles(t_ns, freq);
+
+ if (!of_property_read_u32(np, "smc,nrd_pulse" , &t_ns))
+ cfg->nrd_pulse = ns_to_pulse_cycles(t_ns, freq);
+
+ if (!of_property_read_u32(np, "smc,ncs_write_pulse" , &t_ns))
+ cfg->ncs_write_pulse = ns_to_pulse_cycles(t_ns, freq);
+
+ if (!of_property_read_u32(np, "smc,nwe_pulse" , &t_ns))
+ cfg->nwe_pulse = ns_to_pulse_cycles(t_ns, freq);
+
+ if (!of_property_read_u32(np, "smc,read_cycle" , &t_ns))
+ cfg->read_cycle = ns_to_rw_cycles(t_ns, freq);
+
+ if (!of_property_read_u32(np, "smc,write_cycle" , &t_ns))
+ cfg->write_cycle = ns_to_rw_cycles(t_ns, freq);
+
+ if (!of_property_read_u32(np, "smc,tdf_cycles" , &t_ns))
+ cfg->tdf_cycles = ns_to_cycles(t_ns, freq);
+
+ return get_mode_register_from_dt(smc, np, cfg);
+}
+
+struct converter {
+ const char *name;
+ int (*fn) (struct smc_data *smc, struct device_node *np,
+ struct sam9_smc_config *cfg);
+};
+static const struct converter converters[] = {
+ {"raw", generic_timing_from_dt},
+ {"nanosec", ns_timing_from_dt},
+};
+
+/* Parse and set the timing for this device. */
+static int smc_timing_setup(struct smc_data *smc, struct device_node *np,
+ const struct at91_smc_devtype *devtype)
+{
+ int ret;
+ u32 cs;
+ int i;
+ struct device *dev = smc->dev;
+ const struct converter *converter;
+ const char *converter_name = NULL;
+ struct sam9_smc_config cfg;
+
+ ret = of_property_read_u32(np, "smc,cs" , &cs);
+ if (ret < 0) {
+ dev_err(dev, "missing mandatory property : smc,cs\n");
+ return ret;
+ }
+ if (cs >= devtype->cs_count) {
+ dev_err(dev, "invalid value for property smc,cs (=%d)."
+ "Must be in range 0 to %d\n", cs, devtype->cs_count-1);
+ return -EINVAL;
+ }
+
+ of_property_read_string(np, "smc,converter", &converter_name);
+ if (converter_name) {
+ for (i = 0; i < ARRAY_SIZE(converters); i++)
+ if (strcmp(converters[i].name, converter_name) == 0)
+ converter = &converters[i];
+ if (!converter) {
+ dev_info(dev, "unknown converter. aborting\n");
+ return -EINVAL;
+ }
+ } else {
+ dev_dbg(dev, "cs %d: no smc converter provided. using "
+ "raw register values\n", cs);
+ converter = &converters[0];
+ }
+ dev_dbg(dev, "cs %d using converter : %s\n", cs, converter->name);
+ sam9_smc_cs_read(smc->base + (0x10 * cs), &cfg);
+ converter->fn(smc, np, &cfg);
+ ret = sam9_smc_check_cs_configuration(&cfg);
+ if (ret < 0) {
+ dev_info(dev, "invalid smc configuration for cs %d."
+ "clipping values\n", cs);
+ sam9_smc_clip_cs_configuration(&cfg);
+ dump_timing(smc, &cfg);
+ }
+#ifdef DEBUG
+ else
+ dump_timing(smc, &cfg);
+#endif
+
+ sam9_smc_cs_configure(smc->base + (0x10 * cs), &cfg);
+ return 0;
+}
+
+static int smc_parse_dt(struct smc_data *smc)
+{
+ struct device *dev = smc->dev;
+ const struct of_device_id *of_id = of_match_device(smc_id_table, dev);
+ const struct at91_smc_devtype *devtype = of_id->data;
+ struct device_node *child;
+ int ret;
+
+ for_each_child_of_node(dev->of_node, child) {
+ if (!child->name)
+ continue;
+ if (!of_device_is_available(child))
+ continue;
+ ret = smc_timing_setup(smc, child, devtype);
+ if (ret) {
+ static struct property status = {
+ .name = "status",
+ .value = "disabled",
+ .length = sizeof("disabled"),
+ };
+ dev_err(dev, "%s set timing failed. This node will be disabled.\n",
+ child->full_name);
+ ret = of_update_property(child, &status);
+ if (ret < 0) {
+ dev_err(dev, "can't disable %s. aborting probe\n",
+ child->full_name);
+ break;
+ }
+ }
+ }
+
+ ret = of_platform_populate(dev->of_node, of_default_bus_match_table,
+ NULL, dev);
+ if (ret)
+ dev_err(dev, "%s fail to create devices.\n",
+ dev->of_node->full_name);
+
+ return ret;
+}
+
+static int smc_probe(struct platform_device *pdev)
+{
+ struct resource *res;
+ int ret;
+ void __iomem *base;
+ struct clk *clk;
+ struct smc_data *smc = devm_kzalloc(&pdev->dev, sizeof(struct smc_data),
+ GFP_KERNEL);
+
+ if (!smc)
+ return -ENOMEM;
+
+ smc->dev = &pdev->dev;
+
+ /* get the resource */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ base = devm_request_and_ioremap(&pdev->dev, res);
+ if (IS_ERR(base)) {
+ dev_err(&pdev->dev, "can't map SMC base address\n");
+ return PTR_ERR(base);
+ }
+
+ /* get the clock */
+ clk = devm_clk_get(&pdev->dev, "smc");
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+
+ smc->bus_clk = clk;
+ smc->base = base;
+
+ /* parse the device node */
+ ret = smc_parse_dt(smc);
+ if (!ret)
+ dev_info(&pdev->dev, "Driver registered.\n");
+
+ return ret;
+}
+
+static struct platform_driver smc_driver = {
+ .driver = {
+ .name = "atmel-smc",
+ .owner = THIS_MODULE,
+ .of_match_table = smc_id_table,
+ },
+};
+module_platform_driver_probe(smc_driver, smc_probe);
+
+MODULE_AUTHOR("JJ Hiblot");
+MODULE_DESCRIPTION("Atmel's SMC/EBI driver");
+MODULE_LICENSE("GPL");
--
1.8.5.2

2014-01-09 12:35:44

by Jean-Jacques Hiblot

[permalink] [raw]
Subject: [PATCH v2 08/12] at91: sam9261: Add a clock definition for the smc

Signed-off-by: Jean-Jacques Hiblot <[email protected]>
---
arch/arm/mach-at91/at91sam9261.c | 1 +
1 file changed, 1 insertion(+)

diff --git a/arch/arm/mach-at91/at91sam9261.c b/arch/arm/mach-at91/at91sam9261.c
index a67bfe6..cb5f906 100644
--- a/arch/arm/mach-at91/at91sam9261.c
+++ b/arch/arm/mach-at91/at91sam9261.c
@@ -205,6 +205,7 @@ static struct clk_lookup periph_clocks_lookups[] = {
CLKDEV_CON_DEV_ID(NULL, "fffff400.gpio", &pioA_clk),
CLKDEV_CON_DEV_ID(NULL, "fffff600.gpio", &pioB_clk),
CLKDEV_CON_DEV_ID(NULL, "fffff800.gpio", &pioC_clk),
+ CLKDEV_CON_DEV_ID("smc", "ffffec00.smc", &mck),
};

static struct clk_lookup usart_clocks_lookups[] = {
--
1.8.5.2

2014-01-09 12:35:53

by Jean-Jacques Hiblot

[permalink] [raw]
Subject: [PATCH v2 06/12] at91: smc: Adds helper functions to validate and clip the smc timings.

This patchs implememnts 2 functions to help with the configuration of a
chip-select's timing:
* sam9_smc_check_cs_configuration : checks that the values would fit in the
registers.
* sam9_smc_clip_cs_configuration : clip the values to their maximum.

Signed-off-by: Jean-Jacques Hiblot <[email protected]>
---
arch/arm/mach-at91/include/mach/at91sam9_smc.h | 2 +
arch/arm/mach-at91/sam9_smc.c | 77 ++++++++++++++++++++++++++
2 files changed, 79 insertions(+)

diff --git a/arch/arm/mach-at91/include/mach/at91sam9_smc.h b/arch/arm/mach-at91/include/mach/at91sam9_smc.h
index c3e29311..615ac56 100644
--- a/arch/arm/mach-at91/include/mach/at91sam9_smc.h
+++ b/arch/arm/mach-at91/include/mach/at91sam9_smc.h
@@ -47,6 +47,8 @@ extern void sam9_smc_read_mode(int id, int cs, struct sam9_smc_config *config);
extern void sam9_smc_write_mode(int id, int cs, struct sam9_smc_config *config);
extern void sam9_smc_cs_read(void __iomem *, struct sam9_smc_config *config);
extern void sam9_smc_cs_configure(void __iomem *, struct sam9_smc_config *cfg);
+extern int sam9_smc_check_cs_configuration(struct sam9_smc_config *config);
+extern void sam9_smc_clip_cs_configuration(struct sam9_smc_config *config);
#endif

#define AT91_SMC_SETUP 0x00 /* Setup Register for CS n */
diff --git a/arch/arm/mach-at91/sam9_smc.c b/arch/arm/mach-at91/sam9_smc.c
index d7a6156..fe3c492 100644
--- a/arch/arm/mach-at91/sam9_smc.c
+++ b/arch/arm/mach-at91/sam9_smc.c
@@ -23,6 +23,83 @@

static void __iomem *smc_base_addr[2];

+static int count_trailing_zeroes(u32 x)
+{
+ int ret = 0;
+ if (!(x & 0xFFFF)) {
+ ret += 16;
+ x = x >> 16;
+ }
+ if (!(x & 0xFF)) {
+ ret += 8;
+ x = x >> 8;
+ }
+ if (!(x & 0xF)) {
+ ret += 4;
+ x = x >> 4;
+ }
+ if (!(x & 0x3)) {
+ ret += 2;
+ x = x >> 2;
+ }
+ if (!(x & 0x1)) {
+ ret += 1;
+ x = x >> 1;
+ }
+ if (!(x & 0x1))
+ ret += 1;
+
+ return ret;
+}
+
+
+#define __CHECK_CFG(config, x, y) do {\
+ if (x##_(config->y) > x) {\
+ pr_debug("error: %s (0x%x) is out of range\n", #y,\
+ config->y >> count_trailing_zeroes(x));\
+ return -EINVAL;\
+ } \
+ } while (0)
+
+int sam9_smc_check_cs_configuration(struct sam9_smc_config *config)
+{
+ __CHECK_CFG(config, AT91_SMC_NWESETUP, nwe_setup);
+ __CHECK_CFG(config, AT91_SMC_NCS_WRSETUP, ncs_write_setup);
+ __CHECK_CFG(config, AT91_SMC_NRDSETUP, nrd_setup);
+ __CHECK_CFG(config, AT91_SMC_NCS_RDSETUP, ncs_read_setup);
+ __CHECK_CFG(config, AT91_SMC_NWEPULSE, nwe_pulse);
+ __CHECK_CFG(config, AT91_SMC_NCS_WRPULSE, ncs_write_pulse);
+ __CHECK_CFG(config, AT91_SMC_NRDPULSE, nrd_pulse);
+ __CHECK_CFG(config, AT91_SMC_NCS_RDPULSE, ncs_read_pulse);
+ __CHECK_CFG(config, AT91_SMC_NWECYCLE, write_cycle);
+ __CHECK_CFG(config, AT91_SMC_NRDCYCLE, read_cycle);
+ __CHECK_CFG(config, AT91_SMC_TDF, tdf_cycles);
+ return 0;
+}
+
+#define __CLIP_CFG(config, x, y) do {\
+ if (x##_(config->y) > x) {\
+ config->y = x >> count_trailing_zeroes(x);\
+ pr_debug("clipping %s to %d\n", #y, config->y);\
+ } \
+ } while (0)
+
+void sam9_smc_clip_cs_configuration(struct sam9_smc_config *config)
+{
+ __CLIP_CFG(config, AT91_SMC_NWESETUP, nwe_setup);
+ __CLIP_CFG(config, AT91_SMC_NCS_WRSETUP, ncs_write_setup);
+ __CLIP_CFG(config, AT91_SMC_NRDSETUP, nrd_setup);
+ __CLIP_CFG(config, AT91_SMC_NCS_RDSETUP, ncs_read_setup);
+ __CLIP_CFG(config, AT91_SMC_NWEPULSE, nwe_pulse);
+ __CLIP_CFG(config, AT91_SMC_NCS_WRPULSE, ncs_write_pulse);
+ __CLIP_CFG(config, AT91_SMC_NRDPULSE, nrd_pulse);
+ __CLIP_CFG(config, AT91_SMC_NCS_RDPULSE, ncs_read_pulse);
+ __CLIP_CFG(config, AT91_SMC_NWECYCLE, write_cycle);
+ __CLIP_CFG(config, AT91_SMC_NRDCYCLE, read_cycle);
+ __CLIP_CFG(config, AT91_SMC_TDF, tdf_cycles);
+
+}
+
static void sam9_smc_cs_write_mode(void __iomem *base,
struct sam9_smc_config *config)
{
--
1.8.5.2

2014-01-09 12:36:42

by Jean-Jacques Hiblot

[permalink] [raw]
Subject: [PATCH v2 12/12] at91: dt: sam9261: Added DM9000 in the device tree

Signed-off-by: Jean-Jacques Hiblot <[email protected]>
---
arch/arm/boot/dts/at91sam9261ek.dts | 32 ++++++++++++++++++++++++++++++++
1 file changed, 32 insertions(+)

diff --git a/arch/arm/boot/dts/at91sam9261ek.dts b/arch/arm/boot/dts/at91sam9261ek.dts
index e92cb8e..a3d9d5a 100644
--- a/arch/arm/boot/dts/at91sam9261ek.dts
+++ b/arch/arm/boot/dts/at91sam9261ek.dts
@@ -76,6 +76,38 @@
smc: smc@ffffec00 {
status = "okay";

+ ebi_cs2@2,0 {
+ status = "okay";
+ smc,converter = "nanosec";
+ smc,ncs_read_setup = <0>;
+ smc,nrd_setup = <20>;
+ smc,ncs_write_setup = <0>;
+ smc,nwe_setup = <20>;
+ smc,ncs_read_pulse = <80>;
+ smc,nrd_pulse = <40>;
+ smc,ncs_write_pulse = <80>;
+ smc,nwe_pulse = <40>;
+ smc,read_cycle = <160>;
+ smc,write_cycle = <160>;
+ smc,tdf_cycles = <10>;
+ smc,tdf_optimized = <0>;
+ smc,page_size = <0>;
+ smc,byte_access_type = <1>;
+ smc,bus_width = <1>;
+ smc,nwait_mode = <0>;
+ smc,read_mode = <1>;
+ smc,write_mode = <1>;
+
+ ethernet@2,0 {
+ compatible = "davicom,dm9000";
+ reg = <0x0 0x2 0x4 0x2>;
+ interrupt-parent = <&pioC>;
+ interrupts = <11 IRQ_TYPE_EDGE_BOTH>;
+ local-mac-address = [00 00 de ad be ef];
+ davicom,no-eeprom;
+ };
+ };
+
ebi_cs3@3,0 {
status = "okay";
smc,ncs_read_setup = <0>;
--
1.8.5.2

2014-01-09 12:36:51

by Jean-Jacques Hiblot

[permalink] [raw]
Subject: [PATCH v2 11/12] at91: dt: sam9261: moved the NAND under the smc node

Signed-off-by: Jean-Jacques Hiblot <[email protected]>
---
arch/arm/boot/dts/at91sam9261.dtsi | 35 ++++++++++++++-------------
arch/arm/boot/dts/at91sam9261ek.dts | 48 ++++++++++++++++++++++++++++---------
2 files changed, 55 insertions(+), 28 deletions(-)

diff --git a/arch/arm/boot/dts/at91sam9261.dtsi b/arch/arm/boot/dts/at91sam9261.dtsi
index 6c90790..c558918 100644
--- a/arch/arm/boot/dts/at91sam9261.dtsi
+++ b/arch/arm/boot/dts/at91sam9261.dtsi
@@ -525,23 +525,6 @@
status = "disabled";
};

- nand0: nand@40000000 {
- compatible = "atmel,at91rm9200-nand";
- #address-cells = <1>;
- #size-cells = <1>;
- reg = <0x40000000 0x10000000>;
- atmel,nand-addr-offset = <22>;
- atmel,nand-cmd-offset = <21>;
- pinctrl-names = "default";
- pinctrl-0 = <&pinctrl_nand>;
-
- gpios = <&pioC 15 GPIO_ACTIVE_HIGH
- &pioC 14 GPIO_ACTIVE_HIGH
- 0
- >;
- status = "disabled";
- };
-
smc: smc@ffffec00 {
#address-cells = <2>;
#size-cells = <1>;
@@ -588,6 +571,24 @@
ranges = <0 3 0x00000000 0x10000000>;
smc,cs = <3>;
status = "disabled";
+
+ nand0: nand@3,0 {
+ compatible = "atmel,at91rm9200-nand";
+ #address-cells = <1>;
+ #size-cells = <1>;
+ reg = <0x0 0x10000000>;
+ atmel,nand-addr-offset = <22>;
+ atmel,nand-cmd-offset = <21>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_nand>, <&pinctrl_smc_nandoe>, <&pinctrl_smc_nandwe>;
+ smc,cs = <3>;
+
+ gpios = <&pioC 15 GPIO_ACTIVE_HIGH
+ &pioC 14 GPIO_ACTIVE_HIGH
+ 0
+ >;
+ status = "disabled";
+ };
};
ebi_cs4@4,0 {
#address-cells = <1>;
diff --git a/arch/arm/boot/dts/at91sam9261ek.dts b/arch/arm/boot/dts/at91sam9261ek.dts
index 03c05fc..e92cb8e 100644
--- a/arch/arm/boot/dts/at91sam9261ek.dts
+++ b/arch/arm/boot/dts/at91sam9261ek.dts
@@ -42,17 +42,6 @@
};
};

- nand0: nand@40000000 {
- nand-bus-width = <8>;
- nand-ecc-mode = "soft";
- nand-on-flash-bbt = <1>;
- status = "okay";
- at91bootstrap@0 {
- label = "at91bootstrap";
- reg = <0x0 0x20000>;
- };
- };
-
fb0: fb@0x00600000 {
display = <&display0>;
status = "okay";
@@ -83,6 +72,43 @@
};
};
};
+
+ smc: smc@ffffec00 {
+ status = "okay";
+
+ ebi_cs3@3,0 {
+ status = "okay";
+ smc,ncs_read_setup = <0>;
+ smc,nrd_setup = <1>;
+ smc,ncs_write_setup = <0>;
+ smc,nwe_setup = <1>;
+ smc,ncs_read_pulse = <3>;
+ smc,nrd_pulse = <3>;
+ smc,ncs_write_pulse = <3>;
+ smc,nwe_pulse = <3>;
+ smc,read_cycle = <5>;
+ smc,write_cycle = <5>;
+ smc,tdf_cycles = <2>;
+ smc,tdf_optimized = <0>;
+ smc,page_size = <0>;
+ smc,byte_access_type = <0>;
+ smc,bus_width = <0>;
+ smc,nwait_mode = <0>;
+ smc,read_mode = <1>;
+ smc,write_mode = <1>;
+
+ nand0: nand@3,0 {
+ nand-bus-width = <8>;
+ nand-ecc-mode = "soft";
+ nand-on-flash-bbt = <1>;
+ status = "okay";
+ at91bootstrap@0 {
+ label = "at91bootstrap";
+ reg = <0x0 0x20000>;
+ };
+ };
+ };
+ };
};

leds {
--
1.8.5.2

2014-01-09 12:37:01

by Jean-Jacques Hiblot

[permalink] [raw]
Subject: [PATCH v2 10/12] at91: dt: sam9261: Add an entry in the DT for the SMC/EBI bus driver.

This patch creates a new entry in the device tree for the 9261 under which
should be the described the devices attached to the SMC/EBI.

Signed-off-by: Jean-Jacques Hiblot <[email protected]>
---
arch/arm/boot/dts/at91sam9261.dtsi | 73 ++++++++++++++++++++++++++++++++++++++
1 file changed, 73 insertions(+)

diff --git a/arch/arm/boot/dts/at91sam9261.dtsi b/arch/arm/boot/dts/at91sam9261.dtsi
index 695d5d2..6c90790 100644
--- a/arch/arm/boot/dts/at91sam9261.dtsi
+++ b/arch/arm/boot/dts/at91sam9261.dtsi
@@ -542,6 +542,79 @@
status = "disabled";
};

+ smc: smc@ffffec00 {
+ #address-cells = <2>;
+ #size-cells = <1>;
+ compatible = "atmel,at91sam9261-smc";
+ reg = <0xffffec00 0x80>;
+ pinctrl-names = "default";
+ status = "disabled";
+ ranges = <0 0 0x10000000 0x10000000
+ 1 0 0x20000000 0x10000000
+ 2 0 0x30000000 0x10000000
+ 3 0 0x40000000 0x10000000
+ 4 0 0x50000000 0x10000000
+ 5 0 0x60000000 0x10000000
+ 6 0 0x70000000 0x10000000>;
+
+ ebi_cs0@0,0 {
+ #address-cells = <1>;
+ #size-cells = <1>;
+ compatible = "simple-bus";
+ ranges = <0 0 0x00000000 0x10000000>;
+ smc,cs = <0>;
+ status = "disabled";
+ };
+ ebi_cs1@1,0 {
+ #address-cells = <1>;
+ #size-cells = <1>;
+ compatible = "simple-bus";
+ ranges = <0 1 0x00000000 0x10000000>;
+ smc,cs = <1>;
+ status = "disabled";
+ };
+ ebi_cs2@2,0 {
+ #address-cells = <1>;
+ #size-cells = <1>;
+ compatible = "simple-bus";
+ ranges = <0 2 0x00000000 0x10000000>;
+ smc,cs = <2>;
+ status = "disabled";
+ };
+ ebi_cs3@3,0 {
+ #address-cells = <1>;
+ #size-cells = <1>;
+ compatible = "simple-bus";
+ ranges = <0 3 0x00000000 0x10000000>;
+ smc,cs = <3>;
+ status = "disabled";
+ };
+ ebi_cs4@4,0 {
+ #address-cells = <1>;
+ #size-cells = <1>;
+ compatible = "simple-bus";
+ ranges = <0 4 0x00000000 0x10000000>;
+ smc,cs = <4>;
+ status = "disabled";
+ };
+ ebi_cs5@5,0 {
+ #address-cells = <1>;
+ #size-cells = <1>;
+ compatible = "simple-bus";
+ ranges = <0 5 0x00000000 0x10000000>;
+ smc,cs = <5>;
+ status = "disabled";
+ };
+ ebi_cs6@6,0 {
+ #address-cells = <1>;
+ #size-cells = <1>;
+ compatible = "simple-bus";
+ ranges = <0 6 0x00000000 0x10000000>;
+ smc,cs = <6>;
+ status = "disabled";
+ };
+ };
+
usb0: ohci@00500000 {
compatible = "atmel,at91rm9200-ohci", "usb-ohci";
reg = <0x00500000 0x100000>;
--
1.8.5.2

2014-01-09 12:37:10

by Jean-Jacques Hiblot

[permalink] [raw]
Subject: [PATCH v2 09/12] at91: dt: sam9261: Pinmux DT entries for the SMC/EBI interface

Signed-off-by: Jean-Jacques Hiblot <[email protected]>
---
arch/arm/boot/dts/at91sam9261.dtsi | 54 ++++++++++++++++++++++++++++++++++++++
1 file changed, 54 insertions(+)

diff --git a/arch/arm/boot/dts/at91sam9261.dtsi b/arch/arm/boot/dts/at91sam9261.dtsi
index cd219b9..695d5d2 100644
--- a/arch/arm/boot/dts/at91sam9261.dtsi
+++ b/arch/arm/boot/dts/at91sam9261.dtsi
@@ -317,6 +317,60 @@
>;
};
};
+ smc {
+ pinctrl_smc_nwait: smc_nwait-0 {
+ atmel,pins = <AT91_PIOC 2 AT91_PERIPH_A AT91_PINCTRL_NONE>;
+ };
+ pinctrl_smc_a23_a25: smc_a23_a25-0 {
+ atmel,pins =
+ <AT91_PIOA 30 AT91_PERIPH_B AT91_PINCTRL_NONE
+ AT91_PIOA 31 AT91_PERIPH_B AT91_PINCTRL_NONE
+ AT91_PIOC 3 AT91_PERIPH_A AT91_PINCTRL_NONE>;
+ };
+ pinctrl_smc_d16_d31: smc_d16_d31-0 {
+ atmel,pins =
+ <AT91_PIOC 16 AT91_PERIPH_A AT91_PINCTRL_NONE
+ AT91_PIOC 17 AT91_PERIPH_A AT91_PINCTRL_NONE
+ AT91_PIOC 18 AT91_PERIPH_A AT91_PINCTRL_NONE
+ AT91_PIOC 19 AT91_PERIPH_A AT91_PINCTRL_NONE
+ AT91_PIOC 20 AT91_PERIPH_A AT91_PINCTRL_NONE
+ AT91_PIOC 21 AT91_PERIPH_A AT91_PINCTRL_NONE
+ AT91_PIOC 22 AT91_PERIPH_A AT91_PINCTRL_NONE
+ AT91_PIOC 23 AT91_PERIPH_A AT91_PINCTRL_NONE
+ AT91_PIOC 24 AT91_PERIPH_A AT91_PINCTRL_NONE
+ AT91_PIOC 25 AT91_PERIPH_A AT91_PINCTRL_NONE
+ AT91_PIOC 26 AT91_PERIPH_A AT91_PINCTRL_NONE
+ AT91_PIOC 27 AT91_PERIPH_A AT91_PINCTRL_NONE
+ AT91_PIOC 28 AT91_PERIPH_A AT91_PINCTRL_NONE
+ AT91_PIOC 29 AT91_PERIPH_A AT91_PINCTRL_NONE
+ AT91_PIOC 30 AT91_PERIPH_A AT91_PINCTRL_NONE
+ AT91_PIOC 31 AT91_PERIPH_A AT91_PINCTRL_NONE>;
+ };
+ pinctrl_smc_ncs4: smc_ncs4-0 {
+ atmel,pins = <AT91_PIOC 4 AT91_PERIPH_A AT91_PINCTRL_NONE>;
+ };
+ pinctrl_smc_ncs5: smc_ncs5-0 {
+ atmel,pins = <AT91_PIOC 5 AT91_PERIPH_A AT91_PINCTRL_NONE>;
+ };
+ pinctrl_smc_nandoe: smc_nandoe-0 {
+ atmel,pins = <AT91_PIOC 0 AT91_PERIPH_A AT91_PINCTRL_NONE>;
+ };
+ pinctrl_smc_ncs6: smc_ncs6-0 {
+ atmel,pins = <AT91_PIOC 0 AT91_PERIPH_B AT91_PINCTRL_NONE>;
+ };
+ pinctrl_smc_nandwe: smc_nandwe-0 {
+ atmel,pins = <AT91_PIOC 1 AT91_PERIPH_A AT91_PINCTRL_NONE>;
+ };
+ pinctrl_smc_ncs7: smc_ncs7-0 {
+ atmel,pins = <AT91_PIOC 1 AT91_PERIPH_B AT91_PINCTRL_NONE>;
+ };
+ pinctrl_smc_cfce1: smc_cfce1-0 {
+ atmel,pins = <AT91_PIOC 6 AT91_PERIPH_A AT91_PINCTRL_NONE>;
+ };
+ pinctrl_smc_cfce2: smc_cfce2-0 {
+ atmel,pins = <AT91_PIOC 7 AT91_PERIPH_A AT91_PINCTRL_NONE>;
+ };
+ };
pioA: gpio@fffff400 {
compatible = "atmel,at91rm9200-gpio";
reg = <0xfffff400 0x200>;
--
1.8.5.2

2014-01-09 12:37:56

by Jean-Jacques Hiblot

[permalink] [raw]
Subject: [PATCH v2 05/12] at91: smc: Increased the size of tdf_cycles in struct sam9_smc_config.

This makes it possible to check if the tdf_cycle is too big to fit in the
register.

Signed-off-by: Jean-Jacques Hiblot <[email protected]>
---
arch/arm/mach-at91/include/mach/at91sam9_smc.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/arch/arm/mach-at91/include/mach/at91sam9_smc.h b/arch/arm/mach-at91/include/mach/at91sam9_smc.h
index 56de08a..c3e29311 100644
--- a/arch/arm/mach-at91/include/mach/at91sam9_smc.h
+++ b/arch/arm/mach-at91/include/mach/at91sam9_smc.h
@@ -38,7 +38,7 @@ struct sam9_smc_config {

/* Mode register */
u32 mode;
- u8 tdf_cycles:4;
+ u8 tdf_cycles;
};

extern void sam9_smc_configure(int id, int cs, struct sam9_smc_config *config);
--
1.8.5.2

2014-01-09 12:38:07

by Jean-Jacques Hiblot

[permalink] [raw]
Subject: [PATCH v2 04/12] at91: smc: export sam9_smc_cs_read and sam9_smc_cs_configure.

This patch makes sam9_smc_cs_read and sam9_smc_cs_configure available to
the rest of the kernel (though not to modules).

Signed-off-by: Jean-Jacques Hiblot <[email protected]>
---
arch/arm/mach-at91/include/mach/at91sam9_smc.h | 2 ++
arch/arm/mach-at91/sam9_smc.c | 4 ++--
2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/arch/arm/mach-at91/include/mach/at91sam9_smc.h b/arch/arm/mach-at91/include/mach/at91sam9_smc.h
index 175e1fd..56de08a 100644
--- a/arch/arm/mach-at91/include/mach/at91sam9_smc.h
+++ b/arch/arm/mach-at91/include/mach/at91sam9_smc.h
@@ -45,6 +45,8 @@ extern void sam9_smc_configure(int id, int cs, struct sam9_smc_config *config);
extern void sam9_smc_read(int id, int cs, struct sam9_smc_config *config);
extern void sam9_smc_read_mode(int id, int cs, struct sam9_smc_config *config);
extern void sam9_smc_write_mode(int id, int cs, struct sam9_smc_config *config);
+extern void sam9_smc_cs_read(void __iomem *, struct sam9_smc_config *config);
+extern void sam9_smc_cs_configure(void __iomem *, struct sam9_smc_config *cfg);
#endif

#define AT91_SMC_SETUP 0x00 /* Setup Register for CS n */
diff --git a/arch/arm/mach-at91/sam9_smc.c b/arch/arm/mach-at91/sam9_smc.c
index b26156b..d7a6156 100644
--- a/arch/arm/mach-at91/sam9_smc.c
+++ b/arch/arm/mach-at91/sam9_smc.c
@@ -37,7 +37,7 @@ void sam9_smc_write_mode(int id, int cs,
sam9_smc_cs_write_mode(AT91_SMC_CS(id, cs), config);
}

-static void sam9_smc_cs_configure(void __iomem *base,
+void sam9_smc_cs_configure(void __iomem *base,
struct sam9_smc_config *config)
{

@@ -85,7 +85,7 @@ void sam9_smc_read_mode(int id, int cs,
sam9_smc_cs_read_mode(AT91_SMC_CS(id, cs), config);
}

-static void sam9_smc_cs_read(void __iomem *base,
+void sam9_smc_cs_read(void __iomem *base,
struct sam9_smc_config *config)
{
u32 val;
--
1.8.5.2

2014-01-09 17:00:03

by Boris BREZILLON

[permalink] [raw]
Subject: Re: [PATCH v2 07/12] at91: dt: smc: Added smc bus driver

Hello JJ,

On 09/01/2014 13:31, Jean-Jacques Hiblot wrote:
> The EBI/SMC external interface is used to access external peripherals (NAND
> and Ethernet controller in the case of sam9261ek). Different configurations and
> timings are required for those peripherals. This bus driver can be used to
> setup the bus timings/configuration from the device tree.
> It currently accepts timings in clock period and in nanoseconds.
>
> Signed-off-by: Jean-Jacques Hiblot <[email protected]>
> ---
> drivers/memory/Kconfig | 10 ++
> drivers/memory/Makefile | 1 +
> drivers/memory/atmel-smc.c | 431 +++++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 442 insertions(+)
> create mode 100644 drivers/memory/atmel-smc.c
>
> diff --git a/drivers/memory/Kconfig b/drivers/memory/Kconfig
> index 29a11db..fbdfd63 100644
> --- a/drivers/memory/Kconfig
> +++ b/drivers/memory/Kconfig
> @@ -50,4 +50,14 @@ config TEGRA30_MC
> analysis, especially for IOMMU/SMMU(System Memory Management
> Unit) module.
>
> +config ATMEL_SMC
> + bool "Atmel SMC/EBI driver"
> + default y
> + depends on SOC_AT91SAM9 && OF
> + help
> + Driver for Atmel SMC/EBI controller.
> + Used to configure the EBI (external bus interface) when the device-
> + tree is used. This bus supports NANDs, external ethernet controller,
> + SRAMs, ATA devices, etc.
> +
> endif
> diff --git a/drivers/memory/Makefile b/drivers/memory/Makefile
> index 969d923..101abc4 100644
> --- a/drivers/memory/Makefile
> +++ b/drivers/memory/Makefile
> @@ -9,3 +9,4 @@ obj-$(CONFIG_TI_EMIF) += emif.o
> obj-$(CONFIG_MVEBU_DEVBUS) += mvebu-devbus.o
> obj-$(CONFIG_TEGRA20_MC) += tegra20-mc.o
> obj-$(CONFIG_TEGRA30_MC) += tegra30-mc.o
> +obj-$(CONFIG_ATMEL_SMC) += atmel-smc.o
> diff --git a/drivers/memory/atmel-smc.c b/drivers/memory/atmel-smc.c
> new file mode 100644
> index 0000000..0a1d9ba
> --- /dev/null
> +++ b/drivers/memory/atmel-smc.c
> @@ -0,0 +1,431 @@
> +/*
> + * EBI driver for Atmel SAM9 chips
> + * inspired by the fsl weim bus driver
> + *
> + * Copyright (C) 2013 JJ Hiblot.
> + *
> + * This file is licensed under the terms of the GNU General Public
> + * License version 2. This program is licensed "as is" without any
> + * warranty of any kind, whether express or implied.
> + */
> +#include <linux/module.h>
> +#include <linux/clk.h>
> +#include <linux/io.h>
> +#include <linux/of_device.h>
> +#include <mach/at91sam9_smc.h>

You should avoid machine specific headers inclusions: we're trying to get
rid of them.

Duplicate the code and macros you need in your driver instead.

> +
> +struct smc_data {
> + struct clk *bus_clk;
> + void __iomem *base;
> + struct device *dev;
> +};
> +
> +struct at91_smc_devtype {
> + unsigned int cs_count;
> +};
> +
> +static const struct at91_smc_devtype sam9261_smc_devtype = {
> + .cs_count = 6,
> +};
> +
> +static const struct of_device_id smc_id_table[] = {
> + { .compatible = "atmel,at91sam9261-smc", .data = &sam9261_smc_devtype},
> + { }
> +};
> +MODULE_DEVICE_TABLE(of, smc_id_table);
> +
> +struct smc_parameters_type {
> + const char *name;
> + u16 width;
> + u16 shift;
> +};
> +
> +static const struct smc_parameters_type smc_parameters[] = {
> + {"smc,burst_size", 2, 28},
> + {"smc,burst_enabled", 1, 24},
> + {"smc,tdf_mode", 1, 20},
> + {"smc,bus_width", 2, 12},
> + {"smc,byte_access_type", 1, 8},
> + {"smc,nwait_mode", 2, 4},
> + {"smc,write_mode", 1, 0},
> + {"smc,read_mode", 1, 1},
> + {NULL}
> +};
> +
> +static int get_mode_register_from_dt(struct smc_data *smc,
> + struct device_node *np,
> + struct sam9_smc_config *cfg)
> +{
> + int ret;
> + u32 val;
> + struct device *dev = smc->dev;
> + const struct smc_parameters_type *p = smc_parameters;
> + u32 mode = cfg->mode;
> +
> + while (p->name) {
> + ret = of_property_read_u32(np, p->name , &val);
> + if (ret == -EINVAL) {
> + dev_dbg(dev, "%s: property %s not set.\n", np->name,
> + p->name);
> + p++;
> + continue;
> + } else if (ret) {
> + dev_err(dev, "%s: can't get property %s.\n", np->name,
> + p->name);
> + return ret;
> + }
> + if (val >= (1<<p->width)) {
> + dev_err(dev, "%s: property %s out of range.\n",
> + np->name, p->name);
> + return -ERANGE;
> + }
> + mode &= ~(((1<<p->width)-1) << p->shift);
> + mode |= (val << p->shift);
> + p++;
> + }
> + cfg->mode = mode;
> + return 0;
> +}
> +
> +static int generic_timing_from_dt(struct smc_data *smc, struct device_node *np,
> + struct sam9_smc_config *cfg)
> +{
> + u32 val;
> +
> + if (!of_property_read_u32(np, "smc,ncs_read_setup" , &val))
> + cfg->ncs_read_setup = val;
> +
> + if (!of_property_read_u32(np, "smc,nrd_setup" , &val))
> + cfg->nrd_setup = val;
> +
> + if (!of_property_read_u32(np, "smc,ncs_write_setup" , &val))
> + cfg->ncs_write_setup = val;
> +
> + if (!of_property_read_u32(np, "smc,nwe_setup" , &val))
> + cfg->nwe_setup = val;
> +
> + if (!of_property_read_u32(np, "smc,ncs_read_pulse" , &val))
> + cfg->ncs_read_pulse = val;
> +
> + if (!of_property_read_u32(np, "smc,nrd_pulse" , &val))
> + cfg->nrd_pulse = val;
> +
> + if (!of_property_read_u32(np, "smc,ncs_write_pulse" , &val))
> + cfg->ncs_write_pulse = val;
> +
> + if (!of_property_read_u32(np, "smc,nwe_pulse" , &val))
> + cfg->nwe_pulse = val;
> +
> + if (!of_property_read_u32(np, "smc,read_cycle" , &val))
> + cfg->read_cycle = val;
> +
> + if (!of_property_read_u32(np, "smc,write_cycle" , &val))
> + cfg->write_cycle = val;
> +
> + if (!of_property_read_u32(np, "smc,tdf_cycles" , &val))
> + cfg->tdf_cycles = val;
> +
> + return get_mode_register_from_dt(smc, np, cfg);
> +}
> +
> +/* convert the time in ns in a number of clock cycles */
> +static u32 ns_to_cycles(u32 ns, u32 clk)
> +{
> + /*
> + * convert the clk to kHz for the rest of the calculation to avoid
> + * overflow
> + */
> + u32 clk_kHz = clk / 1000;
> + u32 ncycles = ((ns * clk_kHz) + 1000000 - 1) / 1000000;
What about using an u64 type and do_div ?
> + return ncycles;
> +}
> +
> +static u32 cycles_to_coded_cycle(u32 cycles, int a, int b)
> +{
> + u32 mask_high = (1 << a) - 1;
> + u32 mask_low = (1 << b) - 1;
> + u32 coded;
> +
> + /* check if the value can be described with the coded format */
> + if (cycles & (mask_high & ~mask_low)) {
> + /* not representable. we need to round up */
> + cycles |= mask_high;
> + cycles += 1;
> + }
> + /* Now the value can be represented in the coded format */
> + coded = (cycles & ~mask_high) >> (a - b);
> + coded |= (cycles & mask_low);
> + return coded;
> +}
> +
> +static u32 ns_to_rw_cycles(u32 ns, u32 clk)
> +{
> + return cycles_to_coded_cycle(ns_to_cycles(ns, clk), 8, 7);
> +}
> +
> +static u32 ns_to_pulse_cycles(u32 ns, u32 clk)
> +{
> + return cycles_to_coded_cycle(ns_to_cycles(ns, clk), 8, 6);
> +}
> +
> +static u32 ns_to_setup_cycles(u32 ns, u32 clk)
> +{
> + return cycles_to_coded_cycle(ns_to_cycles(ns, clk), 7, 5);
> +}
> +
> +static u32 cycles_to_ns(u32 cycles, u32 clk)
> +{
> + /*
> + * convert the clk to kHz for the rest of the calculation to avoid
> + * overflow
> + */
> + u32 clk_kHz = clk / 1000;

Ditto (u64 + do_div).
> + return (cycles * 1000000) / clk_kHz;
> +}
> +
> +static u32 coded_cycle_to_cycles(u32 coded, int a, int b)
> +{
> + u32 cycles = (coded >> b) << a;
> + u32 mask_low = (1 << b) - 1;
> + cycles |= (coded & mask_low);
> + return cycles;
> +}
> +
> +static u32 rw_cycles_to_ns(u32 reg, u32 clk)
> +{
> + return cycles_to_ns(coded_cycle_to_cycles(reg, 8, 7), clk);
> +}
> +
> +static u32 pulse_cycles_to_ns(u32 reg, u32 clk)
> +{
> + return cycles_to_ns(coded_cycle_to_cycles(reg, 8, 6), clk);
> +}
> +
> +static u32 setup_cycles_to_ns(u32 reg, u32 clk)
> +{
> + return cycles_to_ns(coded_cycle_to_cycles(reg, 7, 5), clk);
> +}
> +
> +static void dump_timing(struct smc_data *smc, struct sam9_smc_config *config)
> +{
> + u32 freq = clk_get_rate(smc->bus_clk);
> + struct device *dev = smc->dev;
> +
> +#define DUMP(fn, y) dev_info(dev, "%s : 0x%x (%d ns)\n", #y, config->y,\
> + fn(config->y, freq))
> +#define DUMP_PULSE(y) DUMP(pulse_cycles_to_ns, y)
> +#define DUMP_RWCYCLE(y) DUMP(rw_cycles_to_ns, y)
> +#define DUMP_SETUP(y) DUMP(setup_cycles_to_ns, y)
> +#define DUMP_SIMPLE(y) DUMP(cycles_to_ns, y)
> +
> + DUMP_SETUP(nwe_setup);
> + DUMP_SETUP(ncs_write_setup);
> + DUMP_SETUP(nrd_setup);
> + DUMP_SETUP(ncs_read_setup);
> + DUMP_PULSE(nwe_pulse);
> + DUMP_PULSE(ncs_write_pulse);
> + DUMP_PULSE(nrd_pulse);
> + DUMP_PULSE(ncs_read_pulse);
> + DUMP_RWCYCLE(write_cycle);
> + DUMP_RWCYCLE(read_cycle);
> + DUMP_SIMPLE(tdf_cycles);
> +}
> +
> +static int ns_timing_from_dt(struct smc_data *smc, struct device_node *np,
> + struct sam9_smc_config *cfg)
> +{
> + u32 t_ns;
> + u32 freq = clk_get_rate(smc->bus_clk);
> +
> + if (!of_property_read_u32(np, "smc,ncs_read_setup" , &t_ns))
> + cfg->ncs_read_setup = ns_to_setup_cycles(t_ns, freq);
> +
> + if (!of_property_read_u32(np, "smc,nrd_setup" , &t_ns))
> + cfg->nrd_setup = ns_to_setup_cycles(t_ns, freq);
> +
> + if (!of_property_read_u32(np, "smc,ncs_write_setup" , &t_ns))
> + cfg->ncs_write_setup = ns_to_setup_cycles(t_ns, freq);
> +
> + if (!of_property_read_u32(np, "smc,nwe_setup" , &t_ns))
> + cfg->nwe_setup = ns_to_setup_cycles(t_ns, freq);
> +
> + if (!of_property_read_u32(np, "smc,ncs_read_pulse" , &t_ns))
> + cfg->ncs_read_pulse = ns_to_pulse_cycles(t_ns, freq);
> +
> + if (!of_property_read_u32(np, "smc,nrd_pulse" , &t_ns))
> + cfg->nrd_pulse = ns_to_pulse_cycles(t_ns, freq);
> +
> + if (!of_property_read_u32(np, "smc,ncs_write_pulse" , &t_ns))
> + cfg->ncs_write_pulse = ns_to_pulse_cycles(t_ns, freq);
> +
> + if (!of_property_read_u32(np, "smc,nwe_pulse" , &t_ns))
> + cfg->nwe_pulse = ns_to_pulse_cycles(t_ns, freq);
> +
> + if (!of_property_read_u32(np, "smc,read_cycle" , &t_ns))
> + cfg->read_cycle = ns_to_rw_cycles(t_ns, freq);
> +
> + if (!of_property_read_u32(np, "smc,write_cycle" , &t_ns))
> + cfg->write_cycle = ns_to_rw_cycles(t_ns, freq);
> +
> + if (!of_property_read_u32(np, "smc,tdf_cycles" , &t_ns))
> + cfg->tdf_cycles = ns_to_cycles(t_ns, freq);
> +
> + return get_mode_register_from_dt(smc, np, cfg);
> +}
> +
> +struct converter {
> + const char *name;
> + int (*fn) (struct smc_data *smc, struct device_node *np,
> + struct sam9_smc_config *cfg);
> +};
> +static const struct converter converters[] = {
> + {"raw", generic_timing_from_dt},
> + {"nanosec", ns_timing_from_dt},
> +};

Could you use more specific names like:
"atmel,smc-converter-generic"
"atmel,smc-converter-nand"
...

IMHO the timing unit should be specified in the property names:
smc,ncs_read_setup-ns
smc,ncs_read_setup-cycles



> +
> +/* Parse and set the timing for this device. */
> +static int smc_timing_setup(struct smc_data *smc, struct device_node *np,
> + const struct at91_smc_devtype *devtype)
> +{
> + int ret;
> + u32 cs;
> + int i;
> + struct device *dev = smc->dev;
> + const struct converter *converter;
> + const char *converter_name = NULL;
> + struct sam9_smc_config cfg;
> +
> + ret = of_property_read_u32(np, "smc,cs" , &cs);

Shouldn't this be stored in the reg property ?
After all, in your DM9000 patch you use "@cs,offset" to identify the node...

> + if (ret < 0) {
> + dev_err(dev, "missing mandatory property : smc,cs\n");
> + return ret;
> + }
> + if (cs >= devtype->cs_count) {
> + dev_err(dev, "invalid value for property smc,cs (=%d)."
> + "Must be in range 0 to %d\n", cs, devtype->cs_count-1);
> + return -EINVAL;
> + }
> +
> + of_property_read_string(np, "smc,converter", &converter_name);

What about using the "compatible" property + struct of_device_id instead of
"smc,converter" property + struct converter ?

> + if (converter_name) {
> + for (i = 0; i < ARRAY_SIZE(converters); i++)
> + if (strcmp(converters[i].name, converter_name) == 0)
> + converter = &converters[i];
> + if (!converter) {
> + dev_info(dev, "unknown converter. aborting\n");
> + return -EINVAL;
> + }
> + } else {
> + dev_dbg(dev, "cs %d: no smc converter provided. using "
> + "raw register values\n", cs);
> + converter = &converters[0];
> + }
> + dev_dbg(dev, "cs %d using converter : %s\n", cs, converter->name);
> + sam9_smc_cs_read(smc->base + (0x10 * cs), &cfg);
> + converter->fn(smc, np, &cfg);
> + ret = sam9_smc_check_cs_configuration(&cfg);
> + if (ret < 0) {
> + dev_info(dev, "invalid smc configuration for cs %d."
> + "clipping values\n", cs);
> + sam9_smc_clip_cs_configuration(&cfg);
> + dump_timing(smc, &cfg);
> + }
> +#ifdef DEBUG
> + else
> + dump_timing(smc, &cfg);
> +#endif

I'm not a big fan of #ifdef blocks inside the code.
You could define a dummy dump_timing function if DEBUG is not defined:

#ifdef DEBUG

static void dump_timing(struct smc_data *smc, struct sam9_smc_config
*config)
{
/* your implementation */
}

#else

static inline void dump_timing(struct smc_data *smc, struct
sam9_smc_config *config)
{
}

#endif

Or just use dev_dbg when printing things in dump_timing.


> +
> + sam9_smc_cs_configure(smc->base + (0x10 * cs), &cfg);
> + return 0;
> +}
> +
> +static int smc_parse_dt(struct smc_data *smc)
> +{
> + struct device *dev = smc->dev;
> + const struct of_device_id *of_id = of_match_device(smc_id_table, dev);
> + const struct at91_smc_devtype *devtype = of_id->data;
> + struct device_node *child;
> + int ret;
> +
> + for_each_child_of_node(dev->of_node, child) {
> + if (!child->name)
> + continue;
> + if (!of_device_is_available(child))
> + continue;
> + ret = smc_timing_setup(smc, child, devtype);
> + if (ret) {
> + static struct property status = {
> + .name = "status",
> + .value = "disabled",
> + .length = sizeof("disabled"),
> + };
> + dev_err(dev, "%s set timing failed. This node will be disabled.\n",
> + child->full_name);
> + ret = of_update_property(child, &status);
> + if (ret < 0) {
> + dev_err(dev, "can't disable %s. aborting probe\n",
> + child->full_name);
> + break;

The concept of disabling the device if timings cannot be met sounds
interresting...
Let's see what other maintainers say about this :).

> + }
> + }
> + }
> +
> + ret = of_platform_populate(dev->of_node, of_default_bus_match_table,
> + NULL, dev);
> + if (ret)
> + dev_err(dev, "%s fail to create devices.\n",
> + dev->of_node->full_name);
> +
> + return ret;
> +}
> +
> +static int smc_probe(struct platform_device *pdev)
> +{
> + struct resource *res;
> + int ret;
> + void __iomem *base;
> + struct clk *clk;
> + struct smc_data *smc = devm_kzalloc(&pdev->dev, sizeof(struct smc_data),
> + GFP_KERNEL);
> +
> + if (!smc)
> + return -ENOMEM;
> +
> + smc->dev = &pdev->dev;
> +
> + /* get the resource */
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + base = devm_request_and_ioremap(&pdev->dev, res);
> + if (IS_ERR(base)) {
> + dev_err(&pdev->dev, "can't map SMC base address\n");
> + return PTR_ERR(base);
> + }
> +
> + /* get the clock */
> + clk = devm_clk_get(&pdev->dev, "smc");
> + if (IS_ERR(clk))
> + return PTR_ERR(clk);
> +
> + smc->bus_clk = clk;
> + smc->base = base;
> +
> + /* parse the device node */
> + ret = smc_parse_dt(smc);
> + if (!ret)
> + dev_info(&pdev->dev, "Driver registered.\n");
> +
> + return ret;
> +}
> +
> +static struct platform_driver smc_driver = {
> + .driver = {
> + .name = "atmel-smc",
> + .owner = THIS_MODULE,
> + .of_match_table = smc_id_table,
> + },
> +};
> +module_platform_driver_probe(smc_driver, smc_probe);
> +
> +MODULE_AUTHOR("JJ Hiblot");
> +MODULE_DESCRIPTION("Atmel's SMC/EBI driver");
> +MODULE_LICENSE("GPL");


That's all for now. :)

I'll try to test it this week end on a sama5 board.

Best Regards,

Boris

2014-01-09 17:07:37

by Boris BREZILLON

[permalink] [raw]
Subject: Re: [PATCH v2 03/12] at91: dt: sam9261: Added support for the lcd display

On 09/01/2014 13:31, Jean-Jacques Hiblot wrote:
> Signed-off-by: Jean-Jacques Hiblot <[email protected]>
> ---
> arch/arm/boot/dts/at91sam9261.dtsi | 37 ++++++++++++++++++++++++++++++++++++-
> arch/arm/boot/dts/at91sam9261ek.dts | 31 +++++++++++++++++++++++++++++++
> arch/arm/mach-at91/at91sam9261.c | 1 +
> 3 files changed, 68 insertions(+), 1 deletion(-)
>
> diff --git a/arch/arm/boot/dts/at91sam9261.dtsi b/arch/arm/boot/dts/at91sam9261.dtsi
> index 773c3d6..cd219b9 100644
> --- a/arch/arm/boot/dts/at91sam9261.dtsi
> +++ b/arch/arm/boot/dts/at91sam9261.dtsi
> @@ -290,7 +290,33 @@
> atmel,pins = <AT91_PIOC 24 AT91_PERIPH_B AT91_PINCTRL_NONE>;
> };
> };
> -
> + fb {
> + pinctrl_fb: fb-0 {
> + atmel,pins =
> + <AT91_PIOB 1 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB1 periph A */
> + AT91_PIOB 2 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB2 periph A */
> + AT91_PIOB 3 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB3 periph A */
> + AT91_PIOB 7 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB7 periph A */
> + AT91_PIOB 8 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB8 periph A */
> + AT91_PIOB 9 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB9 periph A */
> + AT91_PIOB 10 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB10 periph A */
> + AT91_PIOB 11 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB11 periph A */
> + AT91_PIOB 12 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB12 periph A */
> + AT91_PIOB 15 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB15 periph A */
> + AT91_PIOB 16 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB16 periph A */
> + AT91_PIOB 17 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB17 periph A */
> + AT91_PIOB 18 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB18 periph A */
> + AT91_PIOB 19 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB19 periph A */
> + AT91_PIOB 20 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB20 periph A */
> + AT91_PIOB 23 AT91_PERIPH_B AT91_PINCTRL_NONE /* PB23 periph B */
> + AT91_PIOB 24 AT91_PERIPH_B AT91_PINCTRL_NONE /* PB24 periph B */
> + AT91_PIOB 25 AT91_PERIPH_B AT91_PINCTRL_NONE /* PB25 periph B */
> + AT91_PIOB 26 AT91_PERIPH_B AT91_PINCTRL_NONE /* PB26 periph B */
> + AT91_PIOB 27 AT91_PERIPH_B AT91_PINCTRL_NONE /* PB27 periph B */
> + AT91_PIOB 28 AT91_PERIPH_B AT91_PINCTRL_NONE /* PB28 periph B */
> + >;
> + };
> + };
> pioA: gpio@fffff400 {
> compatible = "atmel,at91rm9200-gpio";
> reg = <0xfffff400 0x200>;
> @@ -436,6 +462,15 @@
> };
> };
>
> + fb0: fb@0x00600000 {
> + compatible = "atmel,at91sam9261-lcdc";
> + reg = <0x00600000 0x1000>;
> + interrupts = <21 IRQ_TYPE_LEVEL_HIGH 3>;
> + pinctrl-names = "default";
> + pinctrl-0 = <&pinctrl_fb>;
> + status = "disabled";
> + };
> +
> nand0: nand@40000000 {
> compatible = "atmel,at91rm9200-nand";
> #address-cells = <1>;
> diff --git a/arch/arm/boot/dts/at91sam9261ek.dts b/arch/arm/boot/dts/at91sam9261ek.dts
> index f3d22a9..03c05fc 100644
> --- a/arch/arm/boot/dts/at91sam9261ek.dts
> +++ b/arch/arm/boot/dts/at91sam9261ek.dts
> @@ -52,6 +52,37 @@
> reg = <0x0 0x20000>;
> };
> };

One more nitpick :
I think this should be taken out in another patch (first 9261 lcdc support,
then ek board lcd def) :p.

> +
> + fb0: fb@0x00600000 {
> + display = <&display0>;
> + status = "okay";
> + atmel,power-control-gpio = <&pioA 12 GPIO_ACTIVE_LOW>;
> + display0: display {
> + bits-per-pixel = <16>;
> + atmel,lcdcon-backlight;
> + atmel,dmacon = <0x1>;
> + atmel,lcdcon2 = <0x80008002>;
> + atmel,guard-time = <1>;
> + atmel,lcd-wiring-mode = "BRG";
> +
> + display-timings {
> + native-mode = <&timing0>;
> + timing0: timing0 {
> + clock-frequency = <4965000>;
> + hactive = <240>;
> + vactive = <320>;
> + hback-porch = <1>;
> + hfront-porch = <33>;
> + vback-porch = <1>;
> + vfront-porch = <0>;
> + hsync-len = <5>;
> + vsync-len = <1>;
> + hsync-active = <1>;
> + vsync-active = <1>;
> + };
> + };
> + };
> + };
> };
>
> leds {
> diff --git a/arch/arm/mach-at91/at91sam9261.c b/arch/arm/mach-at91/at91sam9261.c
> index 200d17a..a67bfe6 100644
> --- a/arch/arm/mach-at91/at91sam9261.c
> +++ b/arch/arm/mach-at91/at91sam9261.c
> @@ -197,6 +197,7 @@ static struct clk_lookup periph_clocks_lookups[] = {
> /* more tc lookup table for DT entries */
> CLKDEV_CON_DEV_ID("t0_clk", "fffa0000.timer", &tc0_clk),
> CLKDEV_CON_DEV_ID("hclk", "500000.ohci", &ohci_clk),
> + CLKDEV_CON_DEV_ID("hclk", "600000.fb", &hck1),
> CLKDEV_CON_DEV_ID("spi_clk", "fffc8000.spi", &spi0_clk),
> CLKDEV_CON_DEV_ID("spi_clk", "fffcc000.spi", &spi1_clk),
> CLKDEV_CON_DEV_ID("mci_clk", "fffa8000.mmc", &mmc_clk),

2014-01-09 21:41:48

by Jean-Jacques Hiblot

[permalink] [raw]
Subject: Re: [PATCH v2 07/12] at91: dt: smc: Added smc bus driver

Hi Boris,

2014/1/9 boris brezillon <[email protected]>:
> Hello JJ,
>
>
> On 09/01/2014 13:31, Jean-Jacques Hiblot wrote:
>>
>> The EBI/SMC external interface is used to access external peripherals
>> (NAND
>> and Ethernet controller in the case of sam9261ek). Different
>> configurations and
>> timings are required for those peripherals. This bus driver can be used to
>> setup the bus timings/configuration from the device tree.
>> It currently accepts timings in clock period and in nanoseconds.
>>
>> Signed-off-by: Jean-Jacques Hiblot <[email protected]>
>> ---
>> drivers/memory/Kconfig | 10 ++
>> drivers/memory/Makefile | 1 +
>> drivers/memory/atmel-smc.c | 431
>> +++++++++++++++++++++++++++++++++++++++++++++
>> 3 files changed, 442 insertions(+)
>> create mode 100644 drivers/memory/atmel-smc.c
>>
>> diff --git a/drivers/memory/Kconfig b/drivers/memory/Kconfig
>> index 29a11db..fbdfd63 100644
>> --- a/drivers/memory/Kconfig
>> +++ b/drivers/memory/Kconfig
>> @@ -50,4 +50,14 @@ config TEGRA30_MC
>> analysis, especially for IOMMU/SMMU(System Memory Management
>> Unit) module.
>> +config ATMEL_SMC
>> + bool "Atmel SMC/EBI driver"
>> + default y
>> + depends on SOC_AT91SAM9 && OF
>> + help
>> + Driver for Atmel SMC/EBI controller.
>> + Used to configure the EBI (external bus interface) when the
>> device-
>> + tree is used. This bus supports NANDs, external ethernet
>> controller,
>> + SRAMs, ATA devices, etc.
>> +
>> endif
>> diff --git a/drivers/memory/Makefile b/drivers/memory/Makefile
>> index 969d923..101abc4 100644
>> --- a/drivers/memory/Makefile
>> +++ b/drivers/memory/Makefile
>> @@ -9,3 +9,4 @@ obj-$(CONFIG_TI_EMIF) += emif.o
>> obj-$(CONFIG_MVEBU_DEVBUS) += mvebu-devbus.o
>> obj-$(CONFIG_TEGRA20_MC) += tegra20-mc.o
>> obj-$(CONFIG_TEGRA30_MC) += tegra30-mc.o
>> +obj-$(CONFIG_ATMEL_SMC) += atmel-smc.o
>> diff --git a/drivers/memory/atmel-smc.c b/drivers/memory/atmel-smc.c
>> new file mode 100644
>> index 0000000..0a1d9ba
>> --- /dev/null
>> +++ b/drivers/memory/atmel-smc.c
>> @@ -0,0 +1,431 @@
>> +/*
>> + * EBI driver for Atmel SAM9 chips
>> + * inspired by the fsl weim bus driver
>> + *
>> + * Copyright (C) 2013 JJ Hiblot.
>> + *
>> + * This file is licensed under the terms of the GNU General Public
>> + * License version 2. This program is licensed "as is" without any
>> + * warranty of any kind, whether express or implied.
>> + */
>> +#include <linux/module.h>
>> +#include <linux/clk.h>
>> +#include <linux/io.h>
>> +#include <linux/of_device.h>
>> +#include <mach/at91sam9_smc.h>
>
>
> You should avoid machine specific headers inclusions: we're trying to get
> rid of them.
>
> Duplicate the code and macros you need in your driver instead.

Is this the right way? We usually try to avoid duplication.

>
>
>> +
>> +struct smc_data {
>> + struct clk *bus_clk;
>> + void __iomem *base;
>> + struct device *dev;
>> +};
>> +
>> +struct at91_smc_devtype {
>> + unsigned int cs_count;
>> +};
>> +
>> +static const struct at91_smc_devtype sam9261_smc_devtype = {
>> + .cs_count = 6,
>> +};
>> +
>> +static const struct of_device_id smc_id_table[] = {
>> + { .compatible = "atmel,at91sam9261-smc", .data =
>> &sam9261_smc_devtype},
>> + { }
>> +};
>> +MODULE_DEVICE_TABLE(of, smc_id_table);
>> +
>> +struct smc_parameters_type {
>> + const char *name;
>> + u16 width;
>> + u16 shift;
>> +};
>> +
>> +static const struct smc_parameters_type smc_parameters[] = {
>> + {"smc,burst_size", 2, 28},
>> + {"smc,burst_enabled", 1, 24},
>> + {"smc,tdf_mode", 1, 20},
>> + {"smc,bus_width", 2, 12},
>> + {"smc,byte_access_type", 1, 8},
>> + {"smc,nwait_mode", 2, 4},
>> + {"smc,write_mode", 1, 0},
>> + {"smc,read_mode", 1, 1},
>> + {NULL}
>> +};
>> +
>> +static int get_mode_register_from_dt(struct smc_data *smc,
>> + struct device_node *np,
>> + struct sam9_smc_config *cfg)
>> +{
>> + int ret;
>> + u32 val;
>> + struct device *dev = smc->dev;
>> + const struct smc_parameters_type *p = smc_parameters;
>> + u32 mode = cfg->mode;
>> +
>> + while (p->name) {
>> + ret = of_property_read_u32(np, p->name , &val);
>> + if (ret == -EINVAL) {
>> + dev_dbg(dev, "%s: property %s not set.\n",
>> np->name,
>> + p->name);
>> + p++;
>> + continue;
>> + } else if (ret) {
>> + dev_err(dev, "%s: can't get property %s.\n",
>> np->name,
>> + p->name);
>> + return ret;
>> + }
>> + if (val >= (1<<p->width)) {
>> + dev_err(dev, "%s: property %s out of range.\n",
>> + np->name, p->name);
>> + return -ERANGE;
>> + }
>> + mode &= ~(((1<<p->width)-1) << p->shift);
>> + mode |= (val << p->shift);
>> + p++;
>> + }
>> + cfg->mode = mode;
>> + return 0;
>> +}
>> +
>> +static int generic_timing_from_dt(struct smc_data *smc, struct
>> device_node *np,
>> + struct sam9_smc_config *cfg)
>> +{
>> + u32 val;
>> +
>> + if (!of_property_read_u32(np, "smc,ncs_read_setup" , &val))
>> + cfg->ncs_read_setup = val;
>> +
>> + if (!of_property_read_u32(np, "smc,nrd_setup" , &val))
>> + cfg->nrd_setup = val;
>> +
>> + if (!of_property_read_u32(np, "smc,ncs_write_setup" , &val))
>> + cfg->ncs_write_setup = val;
>> +
>> + if (!of_property_read_u32(np, "smc,nwe_setup" , &val))
>> + cfg->nwe_setup = val;
>> +
>> + if (!of_property_read_u32(np, "smc,ncs_read_pulse" , &val))
>> + cfg->ncs_read_pulse = val;
>> +
>> + if (!of_property_read_u32(np, "smc,nrd_pulse" , &val))
>> + cfg->nrd_pulse = val;
>> +
>> + if (!of_property_read_u32(np, "smc,ncs_write_pulse" , &val))
>> + cfg->ncs_write_pulse = val;
>> +
>> + if (!of_property_read_u32(np, "smc,nwe_pulse" , &val))
>> + cfg->nwe_pulse = val;
>> +
>> + if (!of_property_read_u32(np, "smc,read_cycle" , &val))
>> + cfg->read_cycle = val;
>> +
>> + if (!of_property_read_u32(np, "smc,write_cycle" , &val))
>> + cfg->write_cycle = val;
>> +
>> + if (!of_property_read_u32(np, "smc,tdf_cycles" , &val))
>> + cfg->tdf_cycles = val;
>> +
>> + return get_mode_register_from_dt(smc, np, cfg);
>> +}
>> +
>> +/* convert the time in ns in a number of clock cycles */
>> +static u32 ns_to_cycles(u32 ns, u32 clk)
>> +{
>> + /*
>> + * convert the clk to kHz for the rest of the calculation to avoid
>> + * overflow
>> + */
>> + u32 clk_kHz = clk / 1000;
>> + u32 ncycles = ((ns * clk_kHz) + 1000000 - 1) / 1000000;
>
> What about using an u64 type and do_div ?

easier and faster (though it's not the point here) this way, and kHz
ist not so imprecise :-)

>
>> + return ncycles;
>> +}
>> +
>> +static u32 cycles_to_coded_cycle(u32 cycles, int a, int b)
>> +{
>> + u32 mask_high = (1 << a) - 1;
>> + u32 mask_low = (1 << b) - 1;
>> + u32 coded;
>> +
>> + /* check if the value can be described with the coded format */
>> + if (cycles & (mask_high & ~mask_low)) {
>> + /* not representable. we need to round up */
>> + cycles |= mask_high;
>> + cycles += 1;
>> + }
>> + /* Now the value can be represented in the coded format */
>> + coded = (cycles & ~mask_high) >> (a - b);
>> + coded |= (cycles & mask_low);
>> + return coded;
>> +}
>> +
>> +static u32 ns_to_rw_cycles(u32 ns, u32 clk)
>> +{
>> + return cycles_to_coded_cycle(ns_to_cycles(ns, clk), 8, 7);
>> +}
>> +
>> +static u32 ns_to_pulse_cycles(u32 ns, u32 clk)
>> +{
>> + return cycles_to_coded_cycle(ns_to_cycles(ns, clk), 8, 6);
>> +}
>> +
>> +static u32 ns_to_setup_cycles(u32 ns, u32 clk)
>> +{
>> + return cycles_to_coded_cycle(ns_to_cycles(ns, clk), 7, 5);
>> +}
>> +
>> +static u32 cycles_to_ns(u32 cycles, u32 clk)
>> +{
>> + /*
>> + * convert the clk to kHz for the rest of the calculation to avoid
>> + * overflow
>> + */
>> + u32 clk_kHz = clk / 1000;
>
>
> Ditto (u64 + do_div).
>
>> + return (cycles * 1000000) / clk_kHz;
>> +}
>> +
>> +static u32 coded_cycle_to_cycles(u32 coded, int a, int b)
>> +{
>> + u32 cycles = (coded >> b) << a;
>> + u32 mask_low = (1 << b) - 1;
>> + cycles |= (coded & mask_low);
>> + return cycles;
>> +}
>> +
>> +static u32 rw_cycles_to_ns(u32 reg, u32 clk)
>> +{
>> + return cycles_to_ns(coded_cycle_to_cycles(reg, 8, 7), clk);
>> +}
>> +
>> +static u32 pulse_cycles_to_ns(u32 reg, u32 clk)
>> +{
>> + return cycles_to_ns(coded_cycle_to_cycles(reg, 8, 6), clk);
>> +}
>> +
>> +static u32 setup_cycles_to_ns(u32 reg, u32 clk)
>> +{
>> + return cycles_to_ns(coded_cycle_to_cycles(reg, 7, 5), clk);
>> +}
>> +
>> +static void dump_timing(struct smc_data *smc, struct sam9_smc_config
>> *config)
>> +{
>> + u32 freq = clk_get_rate(smc->bus_clk);
>> + struct device *dev = smc->dev;
>> +
>> +#define DUMP(fn, y) dev_info(dev, "%s : 0x%x (%d ns)\n", #y,
>> config->y,\
>> + fn(config->y, freq))
>> +#define DUMP_PULSE(y) DUMP(pulse_cycles_to_ns, y)
>> +#define DUMP_RWCYCLE(y) DUMP(rw_cycles_to_ns, y)
>> +#define DUMP_SETUP(y) DUMP(setup_cycles_to_ns, y)
>> +#define DUMP_SIMPLE(y) DUMP(cycles_to_ns, y)
>> +
>> + DUMP_SETUP(nwe_setup);
>> + DUMP_SETUP(ncs_write_setup);
>> + DUMP_SETUP(nrd_setup);
>> + DUMP_SETUP(ncs_read_setup);
>> + DUMP_PULSE(nwe_pulse);
>> + DUMP_PULSE(ncs_write_pulse);
>> + DUMP_PULSE(nrd_pulse);
>> + DUMP_PULSE(ncs_read_pulse);
>> + DUMP_RWCYCLE(write_cycle);
>> + DUMP_RWCYCLE(read_cycle);
>> + DUMP_SIMPLE(tdf_cycles);
>> +}
>> +
>> +static int ns_timing_from_dt(struct smc_data *smc, struct device_node
>> *np,
>> + struct sam9_smc_config *cfg)
>> +{
>> + u32 t_ns;
>> + u32 freq = clk_get_rate(smc->bus_clk);
>> +
>> + if (!of_property_read_u32(np, "smc,ncs_read_setup" , &t_ns))
>> + cfg->ncs_read_setup = ns_to_setup_cycles(t_ns, freq);
>> +
>> + if (!of_property_read_u32(np, "smc,nrd_setup" , &t_ns))
>> + cfg->nrd_setup = ns_to_setup_cycles(t_ns, freq);
>> +
>> + if (!of_property_read_u32(np, "smc,ncs_write_setup" , &t_ns))
>> + cfg->ncs_write_setup = ns_to_setup_cycles(t_ns, freq);
>> +
>> + if (!of_property_read_u32(np, "smc,nwe_setup" , &t_ns))
>> + cfg->nwe_setup = ns_to_setup_cycles(t_ns, freq);
>> +
>> + if (!of_property_read_u32(np, "smc,ncs_read_pulse" , &t_ns))
>> + cfg->ncs_read_pulse = ns_to_pulse_cycles(t_ns, freq);
>> +
>> + if (!of_property_read_u32(np, "smc,nrd_pulse" , &t_ns))
>> + cfg->nrd_pulse = ns_to_pulse_cycles(t_ns, freq);
>> +
>> + if (!of_property_read_u32(np, "smc,ncs_write_pulse" , &t_ns))
>> + cfg->ncs_write_pulse = ns_to_pulse_cycles(t_ns, freq);
>> +
>> + if (!of_property_read_u32(np, "smc,nwe_pulse" , &t_ns))
>> + cfg->nwe_pulse = ns_to_pulse_cycles(t_ns, freq);
>> +
>> + if (!of_property_read_u32(np, "smc,read_cycle" , &t_ns))
>> + cfg->read_cycle = ns_to_rw_cycles(t_ns, freq);
>> +
>> + if (!of_property_read_u32(np, "smc,write_cycle" , &t_ns))
>> + cfg->write_cycle = ns_to_rw_cycles(t_ns, freq);
>> +
>> + if (!of_property_read_u32(np, "smc,tdf_cycles" , &t_ns))
>> + cfg->tdf_cycles = ns_to_cycles(t_ns, freq);
>> +
>> + return get_mode_register_from_dt(smc, np, cfg);
>> +}
>> +
>> +struct converter {
>> + const char *name;
>> + int (*fn) (struct smc_data *smc, struct device_node *np,
>> + struct sam9_smc_config *cfg);
>> +};
>> +static const struct converter converters[] = {
>> + {"raw", generic_timing_from_dt},
>> + {"nanosec", ns_timing_from_dt},
>> +};
>
>
> Could you use more specific names like:
> "atmel,smc-converter-generic"
> "atmel,smc-converter-nand"
> ...
Isn't it a bit redudant? smc,converter = "atmel,smc-converter-generic";

>
> IMHO the timing unit should be specified in the property names:
> smc,ncs_read_setup-ns
> smc,ncs_read_setup-cycles
>
True. Although cycles is misleading. It's more a raw register value.
For pulse, setup and rw cycle, the register value is not identical to
the number of cycles.
>
>
>
>> +
>> +/* Parse and set the timing for this device. */
>> +static int smc_timing_setup(struct smc_data *smc, struct device_node *np,
>> + const struct at91_smc_devtype *devtype)
>> +{
>> + int ret;
>> + u32 cs;
>> + int i;
>> + struct device *dev = smc->dev;
>> + const struct converter *converter;
>> + const char *converter_name = NULL;
>> + struct sam9_smc_config cfg;
>> +
>> + ret = of_property_read_u32(np, "smc,cs" , &cs);
>
>
> Shouldn't this be stored in the reg property ?
> After all, in your DM9000 patch you use "@cs,offset" to identify the node...
True

>
>
>> + if (ret < 0) {
>> + dev_err(dev, "missing mandatory property : smc,cs\n");
>> + return ret;
>> + }
>> + if (cs >= devtype->cs_count) {
>> + dev_err(dev, "invalid value for property smc,cs (=%d)."
>> + "Must be in range 0 to %d\n", cs, devtype->cs_count-1);
>> + return -EINVAL;
>> + }
>> +
>> + of_property_read_string(np, "smc,converter", &converter_name);
>
>
> What about using the "compatible" property + struct of_device_id instead of
> "smc,converter" property + struct converter ?

Because one instance of the driver handles several chip selects and
each may use a different converter.

>
>
>> + if (converter_name) {
>> + for (i = 0; i < ARRAY_SIZE(converters); i++)
>> + if (strcmp(converters[i].name, converter_name) ==
>> 0)
>> + converter = &converters[i];
>> + if (!converter) {
>> + dev_info(dev, "unknown converter. aborting\n");
>> + return -EINVAL;
>> + }
>> + } else {
>> + dev_dbg(dev, "cs %d: no smc converter provided. using "
>> + "raw register values\n", cs);
>> + converter = &converters[0];
>> + }
>> + dev_dbg(dev, "cs %d using converter : %s\n", cs, converter->name);
>> + sam9_smc_cs_read(smc->base + (0x10 * cs), &cfg);
>> + converter->fn(smc, np, &cfg);
>> + ret = sam9_smc_check_cs_configuration(&cfg);
>> + if (ret < 0) {
>> + dev_info(dev, "invalid smc configuration for cs %d."
>> + "clipping values\n", cs);
>> + sam9_smc_clip_cs_configuration(&cfg);
>> + dump_timing(smc, &cfg);
>> + }
>> +#ifdef DEBUG
>> + else
>> + dump_timing(smc, &cfg);
>> +#endif
>
>
> I'm not a big fan of #ifdef blocks inside the code.
> You could define a dummy dump_timing function if DEBUG is not defined:
>
> #ifdef DEBUG
>
>
> static void dump_timing(struct smc_data *smc, struct sam9_smc_config
> *config)
> {
> /* your implementation */
> }
>
> #else
>
> static inline void dump_timing(struct smc_data *smc, struct sam9_smc_config
> *config)
> {
> }
>
> #endif
>
> Or just use dev_dbg when printing things in dump_timing.
>
I wanted to know the values when they were modified (clipped) by the
driver. But it could be removed, knowing that clipping occurred is
enough.
>
>
>> +
>> + sam9_smc_cs_configure(smc->base + (0x10 * cs), &cfg);
>> + return 0;
>> +}
>> +
>> +static int smc_parse_dt(struct smc_data *smc)
>> +{
>> + struct device *dev = smc->dev;
>> + const struct of_device_id *of_id = of_match_device(smc_id_table,
>> dev);
>> + const struct at91_smc_devtype *devtype = of_id->data;
>> + struct device_node *child;
>> + int ret;
>> +
>> + for_each_child_of_node(dev->of_node, child) {
>> + if (!child->name)
>> + continue;
>> + if (!of_device_is_available(child))
>> + continue;
>> + ret = smc_timing_setup(smc, child, devtype);
>> + if (ret) {
>> + static struct property status = {
>> + .name = "status",
>> + .value = "disabled",
>> + .length = sizeof("disabled"),
>> + };
>> + dev_err(dev, "%s set timing failed. This node will
>> be disabled.\n",
>> + child->full_name);
>> + ret = of_update_property(child, &status);
>> + if (ret < 0) {
>> + dev_err(dev, "can't disable %s. aborting
>> probe\n",
>> + child->full_name);
>> + break;
>
>
> The concept of disabling the device if timings cannot be met sounds
> interresting...
> Let's see what other maintainers say about this :).
>
>
>> + }
>> + }
>> + }
>> +
>> + ret = of_platform_populate(dev->of_node,
>> of_default_bus_match_table,
>> + NULL, dev);
>> + if (ret)
>> + dev_err(dev, "%s fail to create devices.\n",
>> + dev->of_node->full_name);
>> +
>> + return ret;
>> +}
>> +
>> +static int smc_probe(struct platform_device *pdev)
>> +{
>> + struct resource *res;
>> + int ret;
>> + void __iomem *base;
>> + struct clk *clk;
>> + struct smc_data *smc = devm_kzalloc(&pdev->dev, sizeof(struct
>> smc_data),
>> + GFP_KERNEL);
>> +
>> + if (!smc)
>> + return -ENOMEM;
>> +
>> + smc->dev = &pdev->dev;
>> +
>> + /* get the resource */
>> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>> + base = devm_request_and_ioremap(&pdev->dev, res);
>> + if (IS_ERR(base)) {
>> + dev_err(&pdev->dev, "can't map SMC base address\n");
>> + return PTR_ERR(base);
>> + }
>> +
>> + /* get the clock */
>> + clk = devm_clk_get(&pdev->dev, "smc");
>> + if (IS_ERR(clk))
>> + return PTR_ERR(clk);
>> +
>> + smc->bus_clk = clk;
>> + smc->base = base;
>> +
>> + /* parse the device node */
>> + ret = smc_parse_dt(smc);
>> + if (!ret)
>> + dev_info(&pdev->dev, "Driver registered.\n");
>> +
>> + return ret;
>> +}
>> +
>> +static struct platform_driver smc_driver = {
>> + .driver = {
>> + .name = "atmel-smc",
>> + .owner = THIS_MODULE,
>> + .of_match_table = smc_id_table,
>> + },
>> +};
>> +module_platform_driver_probe(smc_driver, smc_probe);
>> +
>> +MODULE_AUTHOR("JJ Hiblot");
>> +MODULE_DESCRIPTION("Atmel's SMC/EBI driver");
>> +MODULE_LICENSE("GPL");
>
>
>
> That's all for now. :)
>
> I'll try to test it this week end on a sama5 board.

Thanks

>
> Best Regards,
>
> Boris

2014-01-10 11:14:55

by Jean-Jacques Hiblot

[permalink] [raw]
Subject: Re: [PATCH v2 07/12] at91: dt: smc: Added smc bus driver

2014/1/10 Jean-Jacques Hiblot <[email protected]>:
> 2014/1/9 Jean-Jacques Hiblot <[email protected]>:
>> Hi Boris,
>>
>> 2014/1/9 boris brezillon <[email protected]>:
>>> Hello JJ,
>>>
>>>
>>> On 09/01/2014 13:31, Jean-Jacques Hiblot wrote:
>>>>
>>>> The EBI/SMC external interface is used to access external peripherals
>>>> (NAND
>>>> and Ethernet controller in the case of sam9261ek). Different
>>>> configurations and
>>>> timings are required for those peripherals. This bus driver can be used to
>>>> setup the bus timings/configuration from the device tree.
>>>> It currently accepts timings in clock period and in nanoseconds.
>>>>
>>>> Signed-off-by: Jean-Jacques Hiblot <[email protected]>
>>>> ---
>>>> drivers/memory/Kconfig | 10 ++
>>>> drivers/memory/Makefile | 1 +
>>>> drivers/memory/atmel-smc.c | 431
>>>> +++++++++++++++++++++++++++++++++++++++++++++
>>>> 3 files changed, 442 insertions(+)
>>>> create mode 100644 drivers/memory/atmel-smc.c
>>>>
>>>> diff --git a/drivers/memory/Kconfig b/drivers/memory/Kconfig
>>>> index 29a11db..fbdfd63 100644
>>>> --- a/drivers/memory/Kconfig
>>>> +++ b/drivers/memory/Kconfig
>>>> @@ -50,4 +50,14 @@ config TEGRA30_MC
>>>> analysis, especially for IOMMU/SMMU(System Memory Management
>>>> Unit) module.
>>>> +config ATMEL_SMC
>>>> + bool "Atmel SMC/EBI driver"
>>>> + default y
>>>> + depends on SOC_AT91SAM9 && OF
>>>> + help
>>>> + Driver for Atmel SMC/EBI controller.
>>>> + Used to configure the EBI (external bus interface) when the
>>>> device-
>>>> + tree is used. This bus supports NANDs, external ethernet
>>>> controller,
>>>> + SRAMs, ATA devices, etc.
>>>> +
>>>> endif
>>>> diff --git a/drivers/memory/Makefile b/drivers/memory/Makefile
>>>> index 969d923..101abc4 100644
>>>> --- a/drivers/memory/Makefile
>>>> +++ b/drivers/memory/Makefile
>>>> @@ -9,3 +9,4 @@ obj-$(CONFIG_TI_EMIF) += emif.o
>>>> obj-$(CONFIG_MVEBU_DEVBUS) += mvebu-devbus.o
>>>> obj-$(CONFIG_TEGRA20_MC) += tegra20-mc.o
>>>> obj-$(CONFIG_TEGRA30_MC) += tegra30-mc.o
>>>> +obj-$(CONFIG_ATMEL_SMC) += atmel-smc.o
>>>> diff --git a/drivers/memory/atmel-smc.c b/drivers/memory/atmel-smc.c
>>>> new file mode 100644
>>>> index 0000000..0a1d9ba
>>>> --- /dev/null
>>>> +++ b/drivers/memory/atmel-smc.c
>>>> @@ -0,0 +1,431 @@
>>>> +/*
>>>> + * EBI driver for Atmel SAM9 chips
>>>> + * inspired by the fsl weim bus driver
>>>> + *
>>>> + * Copyright (C) 2013 JJ Hiblot.
>>>> + *
>>>> + * This file is licensed under the terms of the GNU General Public
>>>> + * License version 2. This program is licensed "as is" without any
>>>> + * warranty of any kind, whether express or implied.
>>>> + */
>>>> +#include <linux/module.h>
>>>> +#include <linux/clk.h>
>>>> +#include <linux/io.h>
>>>> +#include <linux/of_device.h>
>>>> +#include <mach/at91sam9_smc.h>
>>>
>>>
>>> You should avoid machine specific headers inclusions: we're trying to get
>>> rid of them.
>>>
>>> Duplicate the code and macros you need in your driver instead.
>>
>> Is this the right way? We usually try to avoid duplication.
>>
>>>
>>>
>>>> +
>>>> +struct smc_data {
>>>> + struct clk *bus_clk;
>>>> + void __iomem *base;
>>>> + struct device *dev;
>>>> +};
>>>> +
>>>> +struct at91_smc_devtype {
>>>> + unsigned int cs_count;
>>>> +};
>>>> +
>>>> +static const struct at91_smc_devtype sam9261_smc_devtype = {
>>>> + .cs_count = 6,
>>>> +};
>>>> +
>>>> +static const struct of_device_id smc_id_table[] = {
>>>> + { .compatible = "atmel,at91sam9261-smc", .data =
>>>> &sam9261_smc_devtype},
>>>> + { }
>>>> +};
>>>> +MODULE_DEVICE_TABLE(of, smc_id_table);
>>>> +
>>>> +struct smc_parameters_type {
>>>> + const char *name;
>>>> + u16 width;
>>>> + u16 shift;
>>>> +};
>>>> +
>>>> +static const struct smc_parameters_type smc_parameters[] = {
>>>> + {"smc,burst_size", 2, 28},
>>>> + {"smc,burst_enabled", 1, 24},
>>>> + {"smc,tdf_mode", 1, 20},
>>>> + {"smc,bus_width", 2, 12},
>>>> + {"smc,byte_access_type", 1, 8},
>>>> + {"smc,nwait_mode", 2, 4},
>>>> + {"smc,write_mode", 1, 0},
>>>> + {"smc,read_mode", 1, 1},
>>>> + {NULL}
>>>> +};
>>>> +
>>>> +static int get_mode_register_from_dt(struct smc_data *smc,
>>>> + struct device_node *np,
>>>> + struct sam9_smc_config *cfg)
>>>> +{
>>>> + int ret;
>>>> + u32 val;
>>>> + struct device *dev = smc->dev;
>>>> + const struct smc_parameters_type *p = smc_parameters;
>>>> + u32 mode = cfg->mode;
>>>> +
>>>> + while (p->name) {
>>>> + ret = of_property_read_u32(np, p->name , &val);
>>>> + if (ret == -EINVAL) {
>>>> + dev_dbg(dev, "%s: property %s not set.\n",
>>>> np->name,
>>>> + p->name);
>>>> + p++;
>>>> + continue;
>>>> + } else if (ret) {
>>>> + dev_err(dev, "%s: can't get property %s.\n",
>>>> np->name,
>>>> + p->name);
>>>> + return ret;
>>>> + }
>>>> + if (val >= (1<<p->width)) {
>>>> + dev_err(dev, "%s: property %s out of range.\n",
>>>> + np->name, p->name);
>>>> + return -ERANGE;
>>>> + }
>>>> + mode &= ~(((1<<p->width)-1) << p->shift);
>>>> + mode |= (val << p->shift);
>>>> + p++;
>>>> + }
>>>> + cfg->mode = mode;
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int generic_timing_from_dt(struct smc_data *smc, struct
>>>> device_node *np,
>>>> + struct sam9_smc_config *cfg)
>>>> +{
>>>> + u32 val;
>>>> +
>>>> + if (!of_property_read_u32(np, "smc,ncs_read_setup" , &val))
>>>> + cfg->ncs_read_setup = val;
>>>> +
>>>> + if (!of_property_read_u32(np, "smc,nrd_setup" , &val))
>>>> + cfg->nrd_setup = val;
>>>> +
>>>> + if (!of_property_read_u32(np, "smc,ncs_write_setup" , &val))
>>>> + cfg->ncs_write_setup = val;
>>>> +
>>>> + if (!of_property_read_u32(np, "smc,nwe_setup" , &val))
>>>> + cfg->nwe_setup = val;
>>>> +
>>>> + if (!of_property_read_u32(np, "smc,ncs_read_pulse" , &val))
>>>> + cfg->ncs_read_pulse = val;
>>>> +
>>>> + if (!of_property_read_u32(np, "smc,nrd_pulse" , &val))
>>>> + cfg->nrd_pulse = val;
>>>> +
>>>> + if (!of_property_read_u32(np, "smc,ncs_write_pulse" , &val))
>>>> + cfg->ncs_write_pulse = val;
>>>> +
>>>> + if (!of_property_read_u32(np, "smc,nwe_pulse" , &val))
>>>> + cfg->nwe_pulse = val;
>>>> +
>>>> + if (!of_property_read_u32(np, "smc,read_cycle" , &val))
>>>> + cfg->read_cycle = val;
>>>> +
>>>> + if (!of_property_read_u32(np, "smc,write_cycle" , &val))
>>>> + cfg->write_cycle = val;
>>>> +
>>>> + if (!of_property_read_u32(np, "smc,tdf_cycles" , &val))
>>>> + cfg->tdf_cycles = val;
>>>> +
>>>> + return get_mode_register_from_dt(smc, np, cfg);
>>>> +}
>>>> +
>>>> +/* convert the time in ns in a number of clock cycles */
>>>> +static u32 ns_to_cycles(u32 ns, u32 clk)
>>>> +{
>>>> + /*
>>>> + * convert the clk to kHz for the rest of the calculation to avoid
>>>> + * overflow
>>>> + */
>>>> + u32 clk_kHz = clk / 1000;
>>>> + u32 ncycles = ((ns * clk_kHz) + 1000000 - 1) / 1000000;
>>>
>>> What about using an u64 type and do_div ?
>>
>> easier and faster (though it's not the point here) this way, and kHz
>> ist not so imprecise :-)
>>
>>>
>>>> + return ncycles;
>>>> +}
>>>> +
>>>> +static u32 cycles_to_coded_cycle(u32 cycles, int a, int b)
>>>> +{
>>>> + u32 mask_high = (1 << a) - 1;
>>>> + u32 mask_low = (1 << b) - 1;
>>>> + u32 coded;
>>>> +
>>>> + /* check if the value can be described with the coded format */
>>>> + if (cycles & (mask_high & ~mask_low)) {
>>>> + /* not representable. we need to round up */
>>>> + cycles |= mask_high;
>>>> + cycles += 1;
>>>> + }
>>>> + /* Now the value can be represented in the coded format */
>>>> + coded = (cycles & ~mask_high) >> (a - b);
>>>> + coded |= (cycles & mask_low);
>>>> + return coded;
>>>> +}
>>>> +
>>>> +static u32 ns_to_rw_cycles(u32 ns, u32 clk)
>>>> +{
>>>> + return cycles_to_coded_cycle(ns_to_cycles(ns, clk), 8, 7);
>>>> +}
>>>> +
>>>> +static u32 ns_to_pulse_cycles(u32 ns, u32 clk)
>>>> +{
>>>> + return cycles_to_coded_cycle(ns_to_cycles(ns, clk), 8, 6);
>>>> +}
>>>> +
>>>> +static u32 ns_to_setup_cycles(u32 ns, u32 clk)
>>>> +{
>>>> + return cycles_to_coded_cycle(ns_to_cycles(ns, clk), 7, 5);
>>>> +}
>>>> +
>>>> +static u32 cycles_to_ns(u32 cycles, u32 clk)
>>>> +{
>>>> + /*
>>>> + * convert the clk to kHz for the rest of the calculation to avoid
>>>> + * overflow
>>>> + */
>>>> + u32 clk_kHz = clk / 1000;
>>>
>>>
>>> Ditto (u64 + do_div).
>>>
>>>> + return (cycles * 1000000) / clk_kHz;
>>>> +}
>>>> +
>>>> +static u32 coded_cycle_to_cycles(u32 coded, int a, int b)
>>>> +{
>>>> + u32 cycles = (coded >> b) << a;
>>>> + u32 mask_low = (1 << b) - 1;
>>>> + cycles |= (coded & mask_low);
>>>> + return cycles;
>>>> +}
>>>> +
>>>> +static u32 rw_cycles_to_ns(u32 reg, u32 clk)
>>>> +{
>>>> + return cycles_to_ns(coded_cycle_to_cycles(reg, 8, 7), clk);
>>>> +}
>>>> +
>>>> +static u32 pulse_cycles_to_ns(u32 reg, u32 clk)
>>>> +{
>>>> + return cycles_to_ns(coded_cycle_to_cycles(reg, 8, 6), clk);
>>>> +}
>>>> +
>>>> +static u32 setup_cycles_to_ns(u32 reg, u32 clk)
>>>> +{
>>>> + return cycles_to_ns(coded_cycle_to_cycles(reg, 7, 5), clk);
>>>> +}
>>>> +
>>>> +static void dump_timing(struct smc_data *smc, struct sam9_smc_config
>>>> *config)
>>>> +{
>>>> + u32 freq = clk_get_rate(smc->bus_clk);
>>>> + struct device *dev = smc->dev;
>>>> +
>>>> +#define DUMP(fn, y) dev_info(dev, "%s : 0x%x (%d ns)\n", #y,
>>>> config->y,\
>>>> + fn(config->y, freq))
>>>> +#define DUMP_PULSE(y) DUMP(pulse_cycles_to_ns, y)
>>>> +#define DUMP_RWCYCLE(y) DUMP(rw_cycles_to_ns, y)
>>>> +#define DUMP_SETUP(y) DUMP(setup_cycles_to_ns, y)
>>>> +#define DUMP_SIMPLE(y) DUMP(cycles_to_ns, y)
>>>> +
>>>> + DUMP_SETUP(nwe_setup);
>>>> + DUMP_SETUP(ncs_write_setup);
>>>> + DUMP_SETUP(nrd_setup);
>>>> + DUMP_SETUP(ncs_read_setup);
>>>> + DUMP_PULSE(nwe_pulse);
>>>> + DUMP_PULSE(ncs_write_pulse);
>>>> + DUMP_PULSE(nrd_pulse);
>>>> + DUMP_PULSE(ncs_read_pulse);
>>>> + DUMP_RWCYCLE(write_cycle);
>>>> + DUMP_RWCYCLE(read_cycle);
>>>> + DUMP_SIMPLE(tdf_cycles);
>>>> +}
>>>> +
>>>> +static int ns_timing_from_dt(struct smc_data *smc, struct device_node
>>>> *np,
>>>> + struct sam9_smc_config *cfg)
>>>> +{
>>>> + u32 t_ns;
>>>> + u32 freq = clk_get_rate(smc->bus_clk);
>>>> +
>>>> + if (!of_property_read_u32(np, "smc,ncs_read_setup" , &t_ns))
>>>> + cfg->ncs_read_setup = ns_to_setup_cycles(t_ns, freq);
>>>> +
>>>> + if (!of_property_read_u32(np, "smc,nrd_setup" , &t_ns))
>>>> + cfg->nrd_setup = ns_to_setup_cycles(t_ns, freq);
>>>> +
>>>> + if (!of_property_read_u32(np, "smc,ncs_write_setup" , &t_ns))
>>>> + cfg->ncs_write_setup = ns_to_setup_cycles(t_ns, freq);
>>>> +
>>>> + if (!of_property_read_u32(np, "smc,nwe_setup" , &t_ns))
>>>> + cfg->nwe_setup = ns_to_setup_cycles(t_ns, freq);
>>>> +
>>>> + if (!of_property_read_u32(np, "smc,ncs_read_pulse" , &t_ns))
>>>> + cfg->ncs_read_pulse = ns_to_pulse_cycles(t_ns, freq);
>>>> +
>>>> + if (!of_property_read_u32(np, "smc,nrd_pulse" , &t_ns))
>>>> + cfg->nrd_pulse = ns_to_pulse_cycles(t_ns, freq);
>>>> +
>>>> + if (!of_property_read_u32(np, "smc,ncs_write_pulse" , &t_ns))
>>>> + cfg->ncs_write_pulse = ns_to_pulse_cycles(t_ns, freq);
>>>> +
>>>> + if (!of_property_read_u32(np, "smc,nwe_pulse" , &t_ns))
>>>> + cfg->nwe_pulse = ns_to_pulse_cycles(t_ns, freq);
>>>> +
>>>> + if (!of_property_read_u32(np, "smc,read_cycle" , &t_ns))
>>>> + cfg->read_cycle = ns_to_rw_cycles(t_ns, freq);
>>>> +
>>>> + if (!of_property_read_u32(np, "smc,write_cycle" , &t_ns))
>>>> + cfg->write_cycle = ns_to_rw_cycles(t_ns, freq);
>>>> +
>>>> + if (!of_property_read_u32(np, "smc,tdf_cycles" , &t_ns))
>>>> + cfg->tdf_cycles = ns_to_cycles(t_ns, freq);
>>>> +
>>>> + return get_mode_register_from_dt(smc, np, cfg);
>>>> +}
>>>> +
>>>> +struct converter {
>>>> + const char *name;
>>>> + int (*fn) (struct smc_data *smc, struct device_node *np,
>>>> + struct sam9_smc_config *cfg);
>>>> +};
>>>> +static const struct converter converters[] = {
>>>> + {"raw", generic_timing_from_dt},
>>>> + {"nanosec", ns_timing_from_dt},
>>>> +};
>>>
>>>
>>> Could you use more specific names like:
>>> "atmel,smc-converter-generic"
>>> "atmel,smc-converter-nand"
>>> ...
>> Isn't it a bit redudant? smc,converter = "atmel,smc-converter-generic";
>>
>>>
>>> IMHO the timing unit should be specified in the property names:
>>> smc,ncs_read_setup-ns
>>> smc,ncs_read_setup-cycles
>>>
>> True. Although cycles is misleading. It's more a raw register value.
>> For pulse, setup and rw cycle, the register value is not identical to
>> the number of cycles.
>>>
>>>
>>>
>>>> +
>>>> +/* Parse and set the timing for this device. */
>>>> +static int smc_timing_setup(struct smc_data *smc, struct device_node *np,
>>>> + const struct at91_smc_devtype *devtype)
>>>> +{
>>>> + int ret;
>>>> + u32 cs;
>>>> + int i;
>>>> + struct device *dev = smc->dev;
>>>> + const struct converter *converter;
>>>> + const char *converter_name = NULL;
>>>> + struct sam9_smc_config cfg;
>>>> +
>>>> + ret = of_property_read_u32(np, "smc,cs" , &cs);
>>>
>>>
>>> Shouldn't this be stored in the reg property ?
>>> After all, in your DM9000 patch you use "@cs,offset" to identify the node...
>> True
> I gave this more thoughts and don't think it should be in a reg
> property. We already have the cs information in the address
> translation. So maybe the CS should extr
sorry for the truncated message. I was about to write:
I think the CS could be extracted from the range property.
>
>>
>>>
>>>
>>>> + if (ret < 0) {
>>>> + dev_err(dev, "missing mandatory property : smc,cs\n");
>>>> + return ret;
>>>> + }
>>>> + if (cs >= devtype->cs_count) {
>>>> + dev_err(dev, "invalid value for property smc,cs (=%d)."
>>>> + "Must be in range 0 to %d\n", cs, devtype->cs_count-1);
>>>> + return -EINVAL;
>>>> + }
>>>> +
>>>> + of_property_read_string(np, "smc,converter", &converter_name);
>>>
>>>
>>> What about using the "compatible" property + struct of_device_id instead of
>>> "smc,converter" property + struct converter ?
>>
>> Because one instance of the driver handles several chip selects and
>> each may use a different converter.
>>
>>>
>>>
>>>> + if (converter_name) {
>>>> + for (i = 0; i < ARRAY_SIZE(converters); i++)
>>>> + if (strcmp(converters[i].name, converter_name) ==
>>>> 0)
>>>> + converter = &converters[i];
>>>> + if (!converter) {
>>>> + dev_info(dev, "unknown converter. aborting\n");
>>>> + return -EINVAL;
>>>> + }
>>>> + } else {
>>>> + dev_dbg(dev, "cs %d: no smc converter provided. using "
>>>> + "raw register values\n", cs);
>>>> + converter = &converters[0];
>>>> + }
>>>> + dev_dbg(dev, "cs %d using converter : %s\n", cs, converter->name);
>>>> + sam9_smc_cs_read(smc->base + (0x10 * cs), &cfg);
>>>> + converter->fn(smc, np, &cfg);
>>>> + ret = sam9_smc_check_cs_configuration(&cfg);
>>>> + if (ret < 0) {
>>>> + dev_info(dev, "invalid smc configuration for cs %d."
>>>> + "clipping values\n", cs);
>>>> + sam9_smc_clip_cs_configuration(&cfg);
>>>> + dump_timing(smc, &cfg);
>>>> + }
>>>> +#ifdef DEBUG
>>>> + else
>>>> + dump_timing(smc, &cfg);
>>>> +#endif
>>>
>>>
>>> I'm not a big fan of #ifdef blocks inside the code.
>>> You could define a dummy dump_timing function if DEBUG is not defined:
>>>
>>> #ifdef DEBUG
>>>
>>>
>>> static void dump_timing(struct smc_data *smc, struct sam9_smc_config
>>> *config)
>>> {
>>> /* your implementation */
>>> }
>>>
>>> #else
>>>
>>> static inline void dump_timing(struct smc_data *smc, struct sam9_smc_config
>>> *config)
>>> {
>>> }
>>>
>>> #endif
>>>
>>> Or just use dev_dbg when printing things in dump_timing.
>>>
>> I wanted to know the values when they were modified (clipped) by the
>> driver. But it could be removed, knowing that clipping occurred is
>> enough.
>>>
>>>
>>>> +
>>>> + sam9_smc_cs_configure(smc->base + (0x10 * cs), &cfg);
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int smc_parse_dt(struct smc_data *smc)
>>>> +{
>>>> + struct device *dev = smc->dev;
>>>> + const struct of_device_id *of_id = of_match_device(smc_id_table,
>>>> dev);
>>>> + const struct at91_smc_devtype *devtype = of_id->data;
>>>> + struct device_node *child;
>>>> + int ret;
>>>> +
>>>> + for_each_child_of_node(dev->of_node, child) {
>>>> + if (!child->name)
>>>> + continue;
>>>> + if (!of_device_is_available(child))
>>>> + continue;
>>>> + ret = smc_timing_setup(smc, child, devtype);
>>>> + if (ret) {
>>>> + static struct property status = {
>>>> + .name = "status",
>>>> + .value = "disabled",
>>>> + .length = sizeof("disabled"),
>>>> + };
>>>> + dev_err(dev, "%s set timing failed. This node will
>>>> be disabled.\n",
>>>> + child->full_name);
>>>> + ret = of_update_property(child, &status);
>>>> + if (ret < 0) {
>>>> + dev_err(dev, "can't disable %s. aborting
>>>> probe\n",
>>>> + child->full_name);
>>>> + break;
>>>
>>>
>>> The concept of disabling the device if timings cannot be met sounds
>>> interresting...
>>> Let's see what other maintainers say about this :).
>>>
>>>
>>>> + }
>>>> + }
>>>> + }
>>>> +
>>>> + ret = of_platform_populate(dev->of_node,
>>>> of_default_bus_match_table,
>>>> + NULL, dev);
>>>> + if (ret)
>>>> + dev_err(dev, "%s fail to create devices.\n",
>>>> + dev->of_node->full_name);
>>>> +
>>>> + return ret;
>>>> +}
>>>> +
>>>> +static int smc_probe(struct platform_device *pdev)
>>>> +{
>>>> + struct resource *res;
>>>> + int ret;
>>>> + void __iomem *base;
>>>> + struct clk *clk;
>>>> + struct smc_data *smc = devm_kzalloc(&pdev->dev, sizeof(struct
>>>> smc_data),
>>>> + GFP_KERNEL);
>>>> +
>>>> + if (!smc)
>>>> + return -ENOMEM;
>>>> +
>>>> + smc->dev = &pdev->dev;
>>>> +
>>>> + /* get the resource */
>>>> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>>>> + base = devm_request_and_ioremap(&pdev->dev, res);
>>>> + if (IS_ERR(base)) {
>>>> + dev_err(&pdev->dev, "can't map SMC base address\n");
>>>> + return PTR_ERR(base);
>>>> + }
>>>> +
>>>> + /* get the clock */
>>>> + clk = devm_clk_get(&pdev->dev, "smc");
>>>> + if (IS_ERR(clk))
>>>> + return PTR_ERR(clk);
>>>> +
>>>> + smc->bus_clk = clk;
>>>> + smc->base = base;
>>>> +
>>>> + /* parse the device node */
>>>> + ret = smc_parse_dt(smc);
>>>> + if (!ret)
>>>> + dev_info(&pdev->dev, "Driver registered.\n");
>>>> +
>>>> + return ret;
>>>> +}
>>>> +
>>>> +static struct platform_driver smc_driver = {
>>>> + .driver = {
>>>> + .name = "atmel-smc",
>>>> + .owner = THIS_MODULE,
>>>> + .of_match_table = smc_id_table,
>>>> + },
>>>> +};
>>>> +module_platform_driver_probe(smc_driver, smc_probe);
>>>> +
>>>> +MODULE_AUTHOR("JJ Hiblot");
>>>> +MODULE_DESCRIPTION("Atmel's SMC/EBI driver");
>>>> +MODULE_LICENSE("GPL");
>>>
>>>
>>>
>>> That's all for now. :)
>>>
>>> I'll try to test it this week end on a sama5 board.
>>
>> Thanks
>>
>>>
>>> Best Regards,
>>>
>>> Boris

2014-01-10 11:18:37

by Jean-Jacques Hiblot

[permalink] [raw]
Subject: Re: [PATCH v2 07/12] at91: dt: smc: Added smc bus driver

2014/1/9 Jean-Jacques Hiblot <[email protected]>:
> Hi Boris,
>
> 2014/1/9 boris brezillon <[email protected]>:
>> Hello JJ,
>>
>>
>> On 09/01/2014 13:31, Jean-Jacques Hiblot wrote:
>>>
>>> The EBI/SMC external interface is used to access external peripherals
>>> (NAND
>>> and Ethernet controller in the case of sam9261ek). Different
>>> configurations and
>>> timings are required for those peripherals. This bus driver can be used to
>>> setup the bus timings/configuration from the device tree.
>>> It currently accepts timings in clock period and in nanoseconds.
>>>
>>> Signed-off-by: Jean-Jacques Hiblot <[email protected]>
>>> ---
>>> drivers/memory/Kconfig | 10 ++
>>> drivers/memory/Makefile | 1 +
>>> drivers/memory/atmel-smc.c | 431
>>> +++++++++++++++++++++++++++++++++++++++++++++
>>> 3 files changed, 442 insertions(+)
>>> create mode 100644 drivers/memory/atmel-smc.c
>>>
>>> diff --git a/drivers/memory/Kconfig b/drivers/memory/Kconfig
>>> index 29a11db..fbdfd63 100644
>>> --- a/drivers/memory/Kconfig
>>> +++ b/drivers/memory/Kconfig
>>> @@ -50,4 +50,14 @@ config TEGRA30_MC
>>> analysis, especially for IOMMU/SMMU(System Memory Management
>>> Unit) module.
>>> +config ATMEL_SMC
>>> + bool "Atmel SMC/EBI driver"
>>> + default y
>>> + depends on SOC_AT91SAM9 && OF
>>> + help
>>> + Driver for Atmel SMC/EBI controller.
>>> + Used to configure the EBI (external bus interface) when the
>>> device-
>>> + tree is used. This bus supports NANDs, external ethernet
>>> controller,
>>> + SRAMs, ATA devices, etc.
>>> +
>>> endif
>>> diff --git a/drivers/memory/Makefile b/drivers/memory/Makefile
>>> index 969d923..101abc4 100644
>>> --- a/drivers/memory/Makefile
>>> +++ b/drivers/memory/Makefile
>>> @@ -9,3 +9,4 @@ obj-$(CONFIG_TI_EMIF) += emif.o
>>> obj-$(CONFIG_MVEBU_DEVBUS) += mvebu-devbus.o
>>> obj-$(CONFIG_TEGRA20_MC) += tegra20-mc.o
>>> obj-$(CONFIG_TEGRA30_MC) += tegra30-mc.o
>>> +obj-$(CONFIG_ATMEL_SMC) += atmel-smc.o
>>> diff --git a/drivers/memory/atmel-smc.c b/drivers/memory/atmel-smc.c
>>> new file mode 100644
>>> index 0000000..0a1d9ba
>>> --- /dev/null
>>> +++ b/drivers/memory/atmel-smc.c
>>> @@ -0,0 +1,431 @@
>>> +/*
>>> + * EBI driver for Atmel SAM9 chips
>>> + * inspired by the fsl weim bus driver
>>> + *
>>> + * Copyright (C) 2013 JJ Hiblot.
>>> + *
>>> + * This file is licensed under the terms of the GNU General Public
>>> + * License version 2. This program is licensed "as is" without any
>>> + * warranty of any kind, whether express or implied.
>>> + */
>>> +#include <linux/module.h>
>>> +#include <linux/clk.h>
>>> +#include <linux/io.h>
>>> +#include <linux/of_device.h>
>>> +#include <mach/at91sam9_smc.h>
>>
>>
>> You should avoid machine specific headers inclusions: we're trying to get
>> rid of them.
>>
>> Duplicate the code and macros you need in your driver instead.
>
> Is this the right way? We usually try to avoid duplication.
>
>>
>>
>>> +
>>> +struct smc_data {
>>> + struct clk *bus_clk;
>>> + void __iomem *base;
>>> + struct device *dev;
>>> +};
>>> +
>>> +struct at91_smc_devtype {
>>> + unsigned int cs_count;
>>> +};
>>> +
>>> +static const struct at91_smc_devtype sam9261_smc_devtype = {
>>> + .cs_count = 6,
>>> +};
>>> +
>>> +static const struct of_device_id smc_id_table[] = {
>>> + { .compatible = "atmel,at91sam9261-smc", .data =
>>> &sam9261_smc_devtype},
>>> + { }
>>> +};
>>> +MODULE_DEVICE_TABLE(of, smc_id_table);
>>> +
>>> +struct smc_parameters_type {
>>> + const char *name;
>>> + u16 width;
>>> + u16 shift;
>>> +};
>>> +
>>> +static const struct smc_parameters_type smc_parameters[] = {
>>> + {"smc,burst_size", 2, 28},
>>> + {"smc,burst_enabled", 1, 24},
>>> + {"smc,tdf_mode", 1, 20},
>>> + {"smc,bus_width", 2, 12},
>>> + {"smc,byte_access_type", 1, 8},
>>> + {"smc,nwait_mode", 2, 4},
>>> + {"smc,write_mode", 1, 0},
>>> + {"smc,read_mode", 1, 1},
>>> + {NULL}
>>> +};
>>> +
>>> +static int get_mode_register_from_dt(struct smc_data *smc,
>>> + struct device_node *np,
>>> + struct sam9_smc_config *cfg)
>>> +{
>>> + int ret;
>>> + u32 val;
>>> + struct device *dev = smc->dev;
>>> + const struct smc_parameters_type *p = smc_parameters;
>>> + u32 mode = cfg->mode;
>>> +
>>> + while (p->name) {
>>> + ret = of_property_read_u32(np, p->name , &val);
>>> + if (ret == -EINVAL) {
>>> + dev_dbg(dev, "%s: property %s not set.\n",
>>> np->name,
>>> + p->name);
>>> + p++;
>>> + continue;
>>> + } else if (ret) {
>>> + dev_err(dev, "%s: can't get property %s.\n",
>>> np->name,
>>> + p->name);
>>> + return ret;
>>> + }
>>> + if (val >= (1<<p->width)) {
>>> + dev_err(dev, "%s: property %s out of range.\n",
>>> + np->name, p->name);
>>> + return -ERANGE;
>>> + }
>>> + mode &= ~(((1<<p->width)-1) << p->shift);
>>> + mode |= (val << p->shift);
>>> + p++;
>>> + }
>>> + cfg->mode = mode;
>>> + return 0;
>>> +}
>>> +
>>> +static int generic_timing_from_dt(struct smc_data *smc, struct
>>> device_node *np,
>>> + struct sam9_smc_config *cfg)
>>> +{
>>> + u32 val;
>>> +
>>> + if (!of_property_read_u32(np, "smc,ncs_read_setup" , &val))
>>> + cfg->ncs_read_setup = val;
>>> +
>>> + if (!of_property_read_u32(np, "smc,nrd_setup" , &val))
>>> + cfg->nrd_setup = val;
>>> +
>>> + if (!of_property_read_u32(np, "smc,ncs_write_setup" , &val))
>>> + cfg->ncs_write_setup = val;
>>> +
>>> + if (!of_property_read_u32(np, "smc,nwe_setup" , &val))
>>> + cfg->nwe_setup = val;
>>> +
>>> + if (!of_property_read_u32(np, "smc,ncs_read_pulse" , &val))
>>> + cfg->ncs_read_pulse = val;
>>> +
>>> + if (!of_property_read_u32(np, "smc,nrd_pulse" , &val))
>>> + cfg->nrd_pulse = val;
>>> +
>>> + if (!of_property_read_u32(np, "smc,ncs_write_pulse" , &val))
>>> + cfg->ncs_write_pulse = val;
>>> +
>>> + if (!of_property_read_u32(np, "smc,nwe_pulse" , &val))
>>> + cfg->nwe_pulse = val;
>>> +
>>> + if (!of_property_read_u32(np, "smc,read_cycle" , &val))
>>> + cfg->read_cycle = val;
>>> +
>>> + if (!of_property_read_u32(np, "smc,write_cycle" , &val))
>>> + cfg->write_cycle = val;
>>> +
>>> + if (!of_property_read_u32(np, "smc,tdf_cycles" , &val))
>>> + cfg->tdf_cycles = val;
>>> +
>>> + return get_mode_register_from_dt(smc, np, cfg);
>>> +}
>>> +
>>> +/* convert the time in ns in a number of clock cycles */
>>> +static u32 ns_to_cycles(u32 ns, u32 clk)
>>> +{
>>> + /*
>>> + * convert the clk to kHz for the rest of the calculation to avoid
>>> + * overflow
>>> + */
>>> + u32 clk_kHz = clk / 1000;
>>> + u32 ncycles = ((ns * clk_kHz) + 1000000 - 1) / 1000000;
>>
>> What about using an u64 type and do_div ?
>
> easier and faster (though it's not the point here) this way, and kHz
> ist not so imprecise :-)
>
>>
>>> + return ncycles;
>>> +}
>>> +
>>> +static u32 cycles_to_coded_cycle(u32 cycles, int a, int b)
>>> +{
>>> + u32 mask_high = (1 << a) - 1;
>>> + u32 mask_low = (1 << b) - 1;
>>> + u32 coded;
>>> +
>>> + /* check if the value can be described with the coded format */
>>> + if (cycles & (mask_high & ~mask_low)) {
>>> + /* not representable. we need to round up */
>>> + cycles |= mask_high;
>>> + cycles += 1;
>>> + }
>>> + /* Now the value can be represented in the coded format */
>>> + coded = (cycles & ~mask_high) >> (a - b);
>>> + coded |= (cycles & mask_low);
>>> + return coded;
>>> +}
>>> +
>>> +static u32 ns_to_rw_cycles(u32 ns, u32 clk)
>>> +{
>>> + return cycles_to_coded_cycle(ns_to_cycles(ns, clk), 8, 7);
>>> +}
>>> +
>>> +static u32 ns_to_pulse_cycles(u32 ns, u32 clk)
>>> +{
>>> + return cycles_to_coded_cycle(ns_to_cycles(ns, clk), 8, 6);
>>> +}
>>> +
>>> +static u32 ns_to_setup_cycles(u32 ns, u32 clk)
>>> +{
>>> + return cycles_to_coded_cycle(ns_to_cycles(ns, clk), 7, 5);
>>> +}
>>> +
>>> +static u32 cycles_to_ns(u32 cycles, u32 clk)
>>> +{
>>> + /*
>>> + * convert the clk to kHz for the rest of the calculation to avoid
>>> + * overflow
>>> + */
>>> + u32 clk_kHz = clk / 1000;
>>
>>
>> Ditto (u64 + do_div).
>>
>>> + return (cycles * 1000000) / clk_kHz;
>>> +}
>>> +
>>> +static u32 coded_cycle_to_cycles(u32 coded, int a, int b)
>>> +{
>>> + u32 cycles = (coded >> b) << a;
>>> + u32 mask_low = (1 << b) - 1;
>>> + cycles |= (coded & mask_low);
>>> + return cycles;
>>> +}
>>> +
>>> +static u32 rw_cycles_to_ns(u32 reg, u32 clk)
>>> +{
>>> + return cycles_to_ns(coded_cycle_to_cycles(reg, 8, 7), clk);
>>> +}
>>> +
>>> +static u32 pulse_cycles_to_ns(u32 reg, u32 clk)
>>> +{
>>> + return cycles_to_ns(coded_cycle_to_cycles(reg, 8, 6), clk);
>>> +}
>>> +
>>> +static u32 setup_cycles_to_ns(u32 reg, u32 clk)
>>> +{
>>> + return cycles_to_ns(coded_cycle_to_cycles(reg, 7, 5), clk);
>>> +}
>>> +
>>> +static void dump_timing(struct smc_data *smc, struct sam9_smc_config
>>> *config)
>>> +{
>>> + u32 freq = clk_get_rate(smc->bus_clk);
>>> + struct device *dev = smc->dev;
>>> +
>>> +#define DUMP(fn, y) dev_info(dev, "%s : 0x%x (%d ns)\n", #y,
>>> config->y,\
>>> + fn(config->y, freq))
>>> +#define DUMP_PULSE(y) DUMP(pulse_cycles_to_ns, y)
>>> +#define DUMP_RWCYCLE(y) DUMP(rw_cycles_to_ns, y)
>>> +#define DUMP_SETUP(y) DUMP(setup_cycles_to_ns, y)
>>> +#define DUMP_SIMPLE(y) DUMP(cycles_to_ns, y)
>>> +
>>> + DUMP_SETUP(nwe_setup);
>>> + DUMP_SETUP(ncs_write_setup);
>>> + DUMP_SETUP(nrd_setup);
>>> + DUMP_SETUP(ncs_read_setup);
>>> + DUMP_PULSE(nwe_pulse);
>>> + DUMP_PULSE(ncs_write_pulse);
>>> + DUMP_PULSE(nrd_pulse);
>>> + DUMP_PULSE(ncs_read_pulse);
>>> + DUMP_RWCYCLE(write_cycle);
>>> + DUMP_RWCYCLE(read_cycle);
>>> + DUMP_SIMPLE(tdf_cycles);
>>> +}
>>> +
>>> +static int ns_timing_from_dt(struct smc_data *smc, struct device_node
>>> *np,
>>> + struct sam9_smc_config *cfg)
>>> +{
>>> + u32 t_ns;
>>> + u32 freq = clk_get_rate(smc->bus_clk);
>>> +
>>> + if (!of_property_read_u32(np, "smc,ncs_read_setup" , &t_ns))
>>> + cfg->ncs_read_setup = ns_to_setup_cycles(t_ns, freq);
>>> +
>>> + if (!of_property_read_u32(np, "smc,nrd_setup" , &t_ns))
>>> + cfg->nrd_setup = ns_to_setup_cycles(t_ns, freq);
>>> +
>>> + if (!of_property_read_u32(np, "smc,ncs_write_setup" , &t_ns))
>>> + cfg->ncs_write_setup = ns_to_setup_cycles(t_ns, freq);
>>> +
>>> + if (!of_property_read_u32(np, "smc,nwe_setup" , &t_ns))
>>> + cfg->nwe_setup = ns_to_setup_cycles(t_ns, freq);
>>> +
>>> + if (!of_property_read_u32(np, "smc,ncs_read_pulse" , &t_ns))
>>> + cfg->ncs_read_pulse = ns_to_pulse_cycles(t_ns, freq);
>>> +
>>> + if (!of_property_read_u32(np, "smc,nrd_pulse" , &t_ns))
>>> + cfg->nrd_pulse = ns_to_pulse_cycles(t_ns, freq);
>>> +
>>> + if (!of_property_read_u32(np, "smc,ncs_write_pulse" , &t_ns))
>>> + cfg->ncs_write_pulse = ns_to_pulse_cycles(t_ns, freq);
>>> +
>>> + if (!of_property_read_u32(np, "smc,nwe_pulse" , &t_ns))
>>> + cfg->nwe_pulse = ns_to_pulse_cycles(t_ns, freq);
>>> +
>>> + if (!of_property_read_u32(np, "smc,read_cycle" , &t_ns))
>>> + cfg->read_cycle = ns_to_rw_cycles(t_ns, freq);
>>> +
>>> + if (!of_property_read_u32(np, "smc,write_cycle" , &t_ns))
>>> + cfg->write_cycle = ns_to_rw_cycles(t_ns, freq);
>>> +
>>> + if (!of_property_read_u32(np, "smc,tdf_cycles" , &t_ns))
>>> + cfg->tdf_cycles = ns_to_cycles(t_ns, freq);
>>> +
>>> + return get_mode_register_from_dt(smc, np, cfg);
>>> +}
>>> +
>>> +struct converter {
>>> + const char *name;
>>> + int (*fn) (struct smc_data *smc, struct device_node *np,
>>> + struct sam9_smc_config *cfg);
>>> +};
>>> +static const struct converter converters[] = {
>>> + {"raw", generic_timing_from_dt},
>>> + {"nanosec", ns_timing_from_dt},
>>> +};
>>
>>
>> Could you use more specific names like:
>> "atmel,smc-converter-generic"
>> "atmel,smc-converter-nand"
>> ...
> Isn't it a bit redudant? smc,converter = "atmel,smc-converter-generic";
>
>>
>> IMHO the timing unit should be specified in the property names:
>> smc,ncs_read_setup-ns
>> smc,ncs_read_setup-cycles
>>
> True. Although cycles is misleading. It's more a raw register value.
> For pulse, setup and rw cycle, the register value is not identical to
> the number of cycles.
>>
>>
>>
>>> +
>>> +/* Parse and set the timing for this device. */
>>> +static int smc_timing_setup(struct smc_data *smc, struct device_node *np,
>>> + const struct at91_smc_devtype *devtype)
>>> +{
>>> + int ret;
>>> + u32 cs;
>>> + int i;
>>> + struct device *dev = smc->dev;
>>> + const struct converter *converter;
>>> + const char *converter_name = NULL;
>>> + struct sam9_smc_config cfg;
>>> +
>>> + ret = of_property_read_u32(np, "smc,cs" , &cs);
>>
>>
>> Shouldn't this be stored in the reg property ?
>> After all, in your DM9000 patch you use "@cs,offset" to identify the node...
> True
I gave this more thoughts and don't think it should be in a reg
property. We already have the cs information in the address
translation. So maybe the CS should extr

>
>>
>>
>>> + if (ret < 0) {
>>> + dev_err(dev, "missing mandatory property : smc,cs\n");
>>> + return ret;
>>> + }
>>> + if (cs >= devtype->cs_count) {
>>> + dev_err(dev, "invalid value for property smc,cs (=%d)."
>>> + "Must be in range 0 to %d\n", cs, devtype->cs_count-1);
>>> + return -EINVAL;
>>> + }
>>> +
>>> + of_property_read_string(np, "smc,converter", &converter_name);
>>
>>
>> What about using the "compatible" property + struct of_device_id instead of
>> "smc,converter" property + struct converter ?
>
> Because one instance of the driver handles several chip selects and
> each may use a different converter.
>
>>
>>
>>> + if (converter_name) {
>>> + for (i = 0; i < ARRAY_SIZE(converters); i++)
>>> + if (strcmp(converters[i].name, converter_name) ==
>>> 0)
>>> + converter = &converters[i];
>>> + if (!converter) {
>>> + dev_info(dev, "unknown converter. aborting\n");
>>> + return -EINVAL;
>>> + }
>>> + } else {
>>> + dev_dbg(dev, "cs %d: no smc converter provided. using "
>>> + "raw register values\n", cs);
>>> + converter = &converters[0];
>>> + }
>>> + dev_dbg(dev, "cs %d using converter : %s\n", cs, converter->name);
>>> + sam9_smc_cs_read(smc->base + (0x10 * cs), &cfg);
>>> + converter->fn(smc, np, &cfg);
>>> + ret = sam9_smc_check_cs_configuration(&cfg);
>>> + if (ret < 0) {
>>> + dev_info(dev, "invalid smc configuration for cs %d."
>>> + "clipping values\n", cs);
>>> + sam9_smc_clip_cs_configuration(&cfg);
>>> + dump_timing(smc, &cfg);
>>> + }
>>> +#ifdef DEBUG
>>> + else
>>> + dump_timing(smc, &cfg);
>>> +#endif
>>
>>
>> I'm not a big fan of #ifdef blocks inside the code.
>> You could define a dummy dump_timing function if DEBUG is not defined:
>>
>> #ifdef DEBUG
>>
>>
>> static void dump_timing(struct smc_data *smc, struct sam9_smc_config
>> *config)
>> {
>> /* your implementation */
>> }
>>
>> #else
>>
>> static inline void dump_timing(struct smc_data *smc, struct sam9_smc_config
>> *config)
>> {
>> }
>>
>> #endif
>>
>> Or just use dev_dbg when printing things in dump_timing.
>>
> I wanted to know the values when they were modified (clipped) by the
> driver. But it could be removed, knowing that clipping occurred is
> enough.
>>
>>
>>> +
>>> + sam9_smc_cs_configure(smc->base + (0x10 * cs), &cfg);
>>> + return 0;
>>> +}
>>> +
>>> +static int smc_parse_dt(struct smc_data *smc)
>>> +{
>>> + struct device *dev = smc->dev;
>>> + const struct of_device_id *of_id = of_match_device(smc_id_table,
>>> dev);
>>> + const struct at91_smc_devtype *devtype = of_id->data;
>>> + struct device_node *child;
>>> + int ret;
>>> +
>>> + for_each_child_of_node(dev->of_node, child) {
>>> + if (!child->name)
>>> + continue;
>>> + if (!of_device_is_available(child))
>>> + continue;
>>> + ret = smc_timing_setup(smc, child, devtype);
>>> + if (ret) {
>>> + static struct property status = {
>>> + .name = "status",
>>> + .value = "disabled",
>>> + .length = sizeof("disabled"),
>>> + };
>>> + dev_err(dev, "%s set timing failed. This node will
>>> be disabled.\n",
>>> + child->full_name);
>>> + ret = of_update_property(child, &status);
>>> + if (ret < 0) {
>>> + dev_err(dev, "can't disable %s. aborting
>>> probe\n",
>>> + child->full_name);
>>> + break;
>>
>>
>> The concept of disabling the device if timings cannot be met sounds
>> interresting...
>> Let's see what other maintainers say about this :).
>>
>>
>>> + }
>>> + }
>>> + }
>>> +
>>> + ret = of_platform_populate(dev->of_node,
>>> of_default_bus_match_table,
>>> + NULL, dev);
>>> + if (ret)
>>> + dev_err(dev, "%s fail to create devices.\n",
>>> + dev->of_node->full_name);
>>> +
>>> + return ret;
>>> +}
>>> +
>>> +static int smc_probe(struct platform_device *pdev)
>>> +{
>>> + struct resource *res;
>>> + int ret;
>>> + void __iomem *base;
>>> + struct clk *clk;
>>> + struct smc_data *smc = devm_kzalloc(&pdev->dev, sizeof(struct
>>> smc_data),
>>> + GFP_KERNEL);
>>> +
>>> + if (!smc)
>>> + return -ENOMEM;
>>> +
>>> + smc->dev = &pdev->dev;
>>> +
>>> + /* get the resource */
>>> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>>> + base = devm_request_and_ioremap(&pdev->dev, res);
>>> + if (IS_ERR(base)) {
>>> + dev_err(&pdev->dev, "can't map SMC base address\n");
>>> + return PTR_ERR(base);
>>> + }
>>> +
>>> + /* get the clock */
>>> + clk = devm_clk_get(&pdev->dev, "smc");
>>> + if (IS_ERR(clk))
>>> + return PTR_ERR(clk);
>>> +
>>> + smc->bus_clk = clk;
>>> + smc->base = base;
>>> +
>>> + /* parse the device node */
>>> + ret = smc_parse_dt(smc);
>>> + if (!ret)
>>> + dev_info(&pdev->dev, "Driver registered.\n");
>>> +
>>> + return ret;
>>> +}
>>> +
>>> +static struct platform_driver smc_driver = {
>>> + .driver = {
>>> + .name = "atmel-smc",
>>> + .owner = THIS_MODULE,
>>> + .of_match_table = smc_id_table,
>>> + },
>>> +};
>>> +module_platform_driver_probe(smc_driver, smc_probe);
>>> +
>>> +MODULE_AUTHOR("JJ Hiblot");
>>> +MODULE_DESCRIPTION("Atmel's SMC/EBI driver");
>>> +MODULE_LICENSE("GPL");
>>
>>
>>
>> That's all for now. :)
>>
>> I'll try to test it this week end on a sama5 board.
>
> Thanks
>
>>
>> Best Regards,
>>
>> Boris

2014-01-11 08:43:36

by Boris BREZILLON

[permalink] [raw]
Subject: Re: [PATCH v2 07/12] at91: dt: smc: Added smc bus driver

On 09/01/2014 22:04, Jean-Jacques Hiblot wrote:
> Hi Boris,
>
> 2014/1/9 boris brezillon <[email protected]>:
>> Hello JJ,
>>
>>
>> On 09/01/2014 13:31, Jean-Jacques Hiblot wrote:
>>> The EBI/SMC external interface is used to access external peripherals
>>> (NAND
>>> and Ethernet controller in the case of sam9261ek). Different
>>> configurations and
>>> timings are required for those peripherals. This bus driver can be used to
>>> setup the bus timings/configuration from the device tree.
>>> It currently accepts timings in clock period and in nanoseconds.
>>>
>>> Signed-off-by: Jean-Jacques Hiblot <[email protected]>
>>> ---
>>> drivers/memory/Kconfig | 10 ++
>>> drivers/memory/Makefile | 1 +
>>> drivers/memory/atmel-smc.c | 431
>>> +++++++++++++++++++++++++++++++++++++++++++++
>>> 3 files changed, 442 insertions(+)
>>> create mode 100644 drivers/memory/atmel-smc.c
>>>
>>> diff --git a/drivers/memory/Kconfig b/drivers/memory/Kconfig
>>> index 29a11db..fbdfd63 100644
>>> --- a/drivers/memory/Kconfig
>>> +++ b/drivers/memory/Kconfig
>>> @@ -50,4 +50,14 @@ config TEGRA30_MC
>>> analysis, especially for IOMMU/SMMU(System Memory Management
>>> Unit) module.
>>> +config ATMEL_SMC
>>> + bool "Atmel SMC/EBI driver"
>>> + default y
>>> + depends on SOC_AT91SAM9 && OF
>>> + help
>>> + Driver for Atmel SMC/EBI controller.
>>> + Used to configure the EBI (external bus interface) when the
>>> device-
>>> + tree is used. This bus supports NANDs, external ethernet
>>> controller,
>>> + SRAMs, ATA devices, etc.
>>> +
>>> endif
>>> diff --git a/drivers/memory/Makefile b/drivers/memory/Makefile
>>> index 969d923..101abc4 100644
>>> --- a/drivers/memory/Makefile
>>> +++ b/drivers/memory/Makefile
>>> @@ -9,3 +9,4 @@ obj-$(CONFIG_TI_EMIF) += emif.o
>>> obj-$(CONFIG_MVEBU_DEVBUS) += mvebu-devbus.o
>>> obj-$(CONFIG_TEGRA20_MC) += tegra20-mc.o
>>> obj-$(CONFIG_TEGRA30_MC) += tegra30-mc.o
>>> +obj-$(CONFIG_ATMEL_SMC) += atmel-smc.o
>>> diff --git a/drivers/memory/atmel-smc.c b/drivers/memory/atmel-smc.c
>>> new file mode 100644
>>> index 0000000..0a1d9ba
>>> --- /dev/null
>>> +++ b/drivers/memory/atmel-smc.c
>>> @@ -0,0 +1,431 @@
>>> +/*
>>> + * EBI driver for Atmel SAM9 chips
>>> + * inspired by the fsl weim bus driver
>>> + *
>>> + * Copyright (C) 2013 JJ Hiblot.
>>> + *
>>> + * This file is licensed under the terms of the GNU General Public
>>> + * License version 2. This program is licensed "as is" without any
>>> + * warranty of any kind, whether express or implied.
>>> + */
>>> +#include <linux/module.h>
>>> +#include <linux/clk.h>
>>> +#include <linux/io.h>
>>> +#include <linux/of_device.h>
>>> +#include <mach/at91sam9_smc.h>
>>
>> You should avoid machine specific headers inclusions: we're trying to get
>> rid of them.
>>
>> Duplicate the code and macros you need in your driver instead.
> Is this the right way?
Not necessarily. But in any case you should not reference machine specific
headers in new drivers, because the ARM architecture maintainers are
trying to get all architecture specific code moved into regular subsystems.

There surely is a lot of pros to this approach (I'll let others detail
these).
The main one I see is that the subsystem maintainer is then able to track
common designs in all available drivers and provide a common framework
in order to :
1) ease other drivers development
2) avoid code duplication

In your case, this leaves 2 solutions:
1) move the sam9_smc fonctions in the new driver and move the sam9_smc
into include/linux/memory (which apparently does not exist).
2) copy all the functions and definition you need in your driver in
order to get
rid of the old implementation, and choose which one to compile
using Kconfig
options.

If you think the sam9_smc existing implementation already match your new
driver
needs, I strongly recommend choosing solution 1, as it will help
smoothly move to your
new driver without having to modify already existing drivers using
sam9_smc functions
(except for the new header path ;)).

> We usually try to avoid duplication.



>
>>
>>> +
>>> +struct smc_data {
>>> + struct clk *bus_clk;
>>> + void __iomem *base;
>>> + struct device *dev;
>>> +};
>>> +
>>> +struct at91_smc_devtype {
>>> + unsigned int cs_count;
>>> +};
>>> +
>>> +static const struct at91_smc_devtype sam9261_smc_devtype = {
>>> + .cs_count = 6,
>>> +};
>>> +
>>> +static const struct of_device_id smc_id_table[] = {
>>> + { .compatible = "atmel,at91sam9261-smc", .data =
>>> &sam9261_smc_devtype},
>>> + { }
>>> +};
>>> +MODULE_DEVICE_TABLE(of, smc_id_table);
>>> +
>>> +struct smc_parameters_type {
>>> + const char *name;
>>> + u16 width;
>>> + u16 shift;
>>> +};
>>> +
>>> +static const struct smc_parameters_type smc_parameters[] = {
>>> + {"smc,burst_size", 2, 28},
>>> + {"smc,burst_enabled", 1, 24},
>>> + {"smc,tdf_mode", 1, 20},
>>> + {"smc,bus_width", 2, 12},
>>> + {"smc,byte_access_type", 1, 8},
>>> + {"smc,nwait_mode", 2, 4},
>>> + {"smc,write_mode", 1, 0},
>>> + {"smc,read_mode", 1, 1},
>>> + {NULL}
>>> +};
>>> +
>>> +static int get_mode_register_from_dt(struct smc_data *smc,
>>> + struct device_node *np,
>>> + struct sam9_smc_config *cfg)
>>> +{
>>> + int ret;
>>> + u32 val;
>>> + struct device *dev = smc->dev;
>>> + const struct smc_parameters_type *p = smc_parameters;
>>> + u32 mode = cfg->mode;
>>> +
>>> + while (p->name) {
>>> + ret = of_property_read_u32(np, p->name , &val);
>>> + if (ret == -EINVAL) {
>>> + dev_dbg(dev, "%s: property %s not set.\n",
>>> np->name,
>>> + p->name);
>>> + p++;
>>> + continue;
>>> + } else if (ret) {
>>> + dev_err(dev, "%s: can't get property %s.\n",
>>> np->name,
>>> + p->name);
>>> + return ret;
>>> + }
>>> + if (val >= (1<<p->width)) {
>>> + dev_err(dev, "%s: property %s out of range.\n",
>>> + np->name, p->name);
>>> + return -ERANGE;
>>> + }
>>> + mode &= ~(((1<<p->width)-1) << p->shift);
>>> + mode |= (val << p->shift);
>>> + p++;
>>> + }
>>> + cfg->mode = mode;
>>> + return 0;
>>> +}
>>> +
>>> +static int generic_timing_from_dt(struct smc_data *smc, struct
>>> device_node *np,
>>> + struct sam9_smc_config *cfg)
>>> +{
>>> + u32 val;
>>> +
>>> + if (!of_property_read_u32(np, "smc,ncs_read_setup" , &val))
>>> + cfg->ncs_read_setup = val;
>>> +
>>> + if (!of_property_read_u32(np, "smc,nrd_setup" , &val))
>>> + cfg->nrd_setup = val;
>>> +
>>> + if (!of_property_read_u32(np, "smc,ncs_write_setup" , &val))
>>> + cfg->ncs_write_setup = val;
>>> +
>>> + if (!of_property_read_u32(np, "smc,nwe_setup" , &val))
>>> + cfg->nwe_setup = val;
>>> +
>>> + if (!of_property_read_u32(np, "smc,ncs_read_pulse" , &val))
>>> + cfg->ncs_read_pulse = val;
>>> +
>>> + if (!of_property_read_u32(np, "smc,nrd_pulse" , &val))
>>> + cfg->nrd_pulse = val;
>>> +
>>> + if (!of_property_read_u32(np, "smc,ncs_write_pulse" , &val))
>>> + cfg->ncs_write_pulse = val;
>>> +
>>> + if (!of_property_read_u32(np, "smc,nwe_pulse" , &val))
>>> + cfg->nwe_pulse = val;
>>> +
>>> + if (!of_property_read_u32(np, "smc,read_cycle" , &val))
>>> + cfg->read_cycle = val;
>>> +
>>> + if (!of_property_read_u32(np, "smc,write_cycle" , &val))
>>> + cfg->write_cycle = val;
>>> +
>>> + if (!of_property_read_u32(np, "smc,tdf_cycles" , &val))
>>> + cfg->tdf_cycles = val;
>>> +
>>> + return get_mode_register_from_dt(smc, np, cfg);
>>> +}
>>> +
>>> +/* convert the time in ns in a number of clock cycles */
>>> +static u32 ns_to_cycles(u32 ns, u32 clk)
>>> +{
>>> + /*
>>> + * convert the clk to kHz for the rest of the calculation to avoid
>>> + * overflow
>>> + */
>>> + u32 clk_kHz = clk / 1000;
>>> + u32 ncycles = ((ns * clk_kHz) + 1000000 - 1) / 1000000;
>> What about using an u64 type and do_div ?
> easier and faster (though it's not the point here) this way, and kHz
> ist not so imprecise :-)
>
>>> + return ncycles;
>>> +}
>>> +
>>> +static u32 cycles_to_coded_cycle(u32 cycles, int a, int b)
>>> +{
>>> + u32 mask_high = (1 << a) - 1;
>>> + u32 mask_low = (1 << b) - 1;
>>> + u32 coded;
>>> +
>>> + /* check if the value can be described with the coded format */
>>> + if (cycles & (mask_high & ~mask_low)) {
>>> + /* not representable. we need to round up */
>>> + cycles |= mask_high;
>>> + cycles += 1;
>>> + }
>>> + /* Now the value can be represented in the coded format */
>>> + coded = (cycles & ~mask_high) >> (a - b);
>>> + coded |= (cycles & mask_low);
>>> + return coded;
>>> +}
>>> +
>>> +static u32 ns_to_rw_cycles(u32 ns, u32 clk)
>>> +{
>>> + return cycles_to_coded_cycle(ns_to_cycles(ns, clk), 8, 7);
>>> +}
>>> +
>>> +static u32 ns_to_pulse_cycles(u32 ns, u32 clk)
>>> +{
>>> + return cycles_to_coded_cycle(ns_to_cycles(ns, clk), 8, 6);
>>> +}
>>> +
>>> +static u32 ns_to_setup_cycles(u32 ns, u32 clk)
>>> +{
>>> + return cycles_to_coded_cycle(ns_to_cycles(ns, clk), 7, 5);
>>> +}
>>> +
>>> +static u32 cycles_to_ns(u32 cycles, u32 clk)
>>> +{
>>> + /*
>>> + * convert the clk to kHz for the rest of the calculation to avoid
>>> + * overflow
>>> + */
>>> + u32 clk_kHz = clk / 1000;
>>
>> Ditto (u64 + do_div).
>>
>>> + return (cycles * 1000000) / clk_kHz;
>>> +}
>>> +
>>> +static u32 coded_cycle_to_cycles(u32 coded, int a, int b)
>>> +{
>>> + u32 cycles = (coded >> b) << a;
>>> + u32 mask_low = (1 << b) - 1;
>>> + cycles |= (coded & mask_low);
>>> + return cycles;
>>> +}
>>> +
>>> +static u32 rw_cycles_to_ns(u32 reg, u32 clk)
>>> +{
>>> + return cycles_to_ns(coded_cycle_to_cycles(reg, 8, 7), clk);
>>> +}
>>> +
>>> +static u32 pulse_cycles_to_ns(u32 reg, u32 clk)
>>> +{
>>> + return cycles_to_ns(coded_cycle_to_cycles(reg, 8, 6), clk);
>>> +}
>>> +
>>> +static u32 setup_cycles_to_ns(u32 reg, u32 clk)
>>> +{
>>> + return cycles_to_ns(coded_cycle_to_cycles(reg, 7, 5), clk);
>>> +}
>>> +
>>> +static void dump_timing(struct smc_data *smc, struct sam9_smc_config
>>> *config)
>>> +{
>>> + u32 freq = clk_get_rate(smc->bus_clk);
>>> + struct device *dev = smc->dev;
>>> +
>>> +#define DUMP(fn, y) dev_info(dev, "%s : 0x%x (%d ns)\n", #y,
>>> config->y,\
>>> + fn(config->y, freq))
>>> +#define DUMP_PULSE(y) DUMP(pulse_cycles_to_ns, y)
>>> +#define DUMP_RWCYCLE(y) DUMP(rw_cycles_to_ns, y)
>>> +#define DUMP_SETUP(y) DUMP(setup_cycles_to_ns, y)
>>> +#define DUMP_SIMPLE(y) DUMP(cycles_to_ns, y)
>>> +
>>> + DUMP_SETUP(nwe_setup);
>>> + DUMP_SETUP(ncs_write_setup);
>>> + DUMP_SETUP(nrd_setup);
>>> + DUMP_SETUP(ncs_read_setup);
>>> + DUMP_PULSE(nwe_pulse);
>>> + DUMP_PULSE(ncs_write_pulse);
>>> + DUMP_PULSE(nrd_pulse);
>>> + DUMP_PULSE(ncs_read_pulse);
>>> + DUMP_RWCYCLE(write_cycle);
>>> + DUMP_RWCYCLE(read_cycle);
>>> + DUMP_SIMPLE(tdf_cycles);
>>> +}
>>> +
>>> +static int ns_timing_from_dt(struct smc_data *smc, struct device_node
>>> *np,
>>> + struct sam9_smc_config *cfg)
>>> +{
>>> + u32 t_ns;
>>> + u32 freq = clk_get_rate(smc->bus_clk);
>>> +
>>> + if (!of_property_read_u32(np, "smc,ncs_read_setup" , &t_ns))
>>> + cfg->ncs_read_setup = ns_to_setup_cycles(t_ns, freq);
>>> +
>>> + if (!of_property_read_u32(np, "smc,nrd_setup" , &t_ns))
>>> + cfg->nrd_setup = ns_to_setup_cycles(t_ns, freq);
>>> +
>>> + if (!of_property_read_u32(np, "smc,ncs_write_setup" , &t_ns))
>>> + cfg->ncs_write_setup = ns_to_setup_cycles(t_ns, freq);
>>> +
>>> + if (!of_property_read_u32(np, "smc,nwe_setup" , &t_ns))
>>> + cfg->nwe_setup = ns_to_setup_cycles(t_ns, freq);
>>> +
>>> + if (!of_property_read_u32(np, "smc,ncs_read_pulse" , &t_ns))
>>> + cfg->ncs_read_pulse = ns_to_pulse_cycles(t_ns, freq);
>>> +
>>> + if (!of_property_read_u32(np, "smc,nrd_pulse" , &t_ns))
>>> + cfg->nrd_pulse = ns_to_pulse_cycles(t_ns, freq);
>>> +
>>> + if (!of_property_read_u32(np, "smc,ncs_write_pulse" , &t_ns))
>>> + cfg->ncs_write_pulse = ns_to_pulse_cycles(t_ns, freq);
>>> +
>>> + if (!of_property_read_u32(np, "smc,nwe_pulse" , &t_ns))
>>> + cfg->nwe_pulse = ns_to_pulse_cycles(t_ns, freq);
>>> +
>>> + if (!of_property_read_u32(np, "smc,read_cycle" , &t_ns))
>>> + cfg->read_cycle = ns_to_rw_cycles(t_ns, freq);
>>> +
>>> + if (!of_property_read_u32(np, "smc,write_cycle" , &t_ns))
>>> + cfg->write_cycle = ns_to_rw_cycles(t_ns, freq);
>>> +
>>> + if (!of_property_read_u32(np, "smc,tdf_cycles" , &t_ns))
>>> + cfg->tdf_cycles = ns_to_cycles(t_ns, freq);
>>> +
>>> + return get_mode_register_from_dt(smc, np, cfg);
>>> +}
>>> +
>>> +struct converter {
>>> + const char *name;
>>> + int (*fn) (struct smc_data *smc, struct device_node *np,
>>> + struct sam9_smc_config *cfg);
>>> +};
>>> +static const struct converter converters[] = {
>>> + {"raw", generic_timing_from_dt},
>>> + {"nanosec", ns_timing_from_dt},
>>> +};
>>
>> Could you use more specific names like:
>> "atmel,smc-converter-generic"
>> "atmel,smc-converter-nand"
>> ...
> Isn't it a bit redudant? smc,converter = "atmel,smc-converter-generic";
>
>> IMHO the timing unit should be specified in the property names:
>> smc,ncs_read_setup-ns
>> smc,ncs_read_setup-cycles
>>
> True. Although cycles is misleading. It's more a raw register value.
> For pulse, setup and rw cycle, the register value is not identical to
> the number of cycles.
>>
>>
>>> +
>>> +/* Parse and set the timing for this device. */
>>> +static int smc_timing_setup(struct smc_data *smc, struct device_node *np,
>>> + const struct at91_smc_devtype *devtype)
>>> +{
>>> + int ret;
>>> + u32 cs;
>>> + int i;
>>> + struct device *dev = smc->dev;
>>> + const struct converter *converter;
>>> + const char *converter_name = NULL;
>>> + struct sam9_smc_config cfg;
>>> +
>>> + ret = of_property_read_u32(np, "smc,cs" , &cs);
>>
>> Shouldn't this be stored in the reg property ?
>> After all, in your DM9000 patch you use "@cs,offset" to identify the node...
> True
>
>>
>>> + if (ret < 0) {
>>> + dev_err(dev, "missing mandatory property : smc,cs\n");
>>> + return ret;
>>> + }
>>> + if (cs >= devtype->cs_count) {
>>> + dev_err(dev, "invalid value for property smc,cs (=%d)."
>>> + "Must be in range 0 to %d\n", cs, devtype->cs_count-1);
>>> + return -EINVAL;
>>> + }
>>> +
>>> + of_property_read_string(np, "smc,converter", &converter_name);
>>
>> What about using the "compatible" property + struct of_device_id instead of
>> "smc,converter" property + struct converter ?
> Because one instance of the driver handles several chip selects and
> each may use a different converter.
>
>>
>>> + if (converter_name) {
>>> + for (i = 0; i < ARRAY_SIZE(converters); i++)
>>> + if (strcmp(converters[i].name, converter_name) ==
>>> 0)
>>> + converter = &converters[i];
>>> + if (!converter) {
>>> + dev_info(dev, "unknown converter. aborting\n");
>>> + return -EINVAL;
>>> + }
>>> + } else {
>>> + dev_dbg(dev, "cs %d: no smc converter provided. using "
>>> + "raw register values\n", cs);
>>> + converter = &converters[0];
>>> + }
>>> + dev_dbg(dev, "cs %d using converter : %s\n", cs, converter->name);
>>> + sam9_smc_cs_read(smc->base + (0x10 * cs), &cfg);
>>> + converter->fn(smc, np, &cfg);
>>> + ret = sam9_smc_check_cs_configuration(&cfg);
>>> + if (ret < 0) {
>>> + dev_info(dev, "invalid smc configuration for cs %d."
>>> + "clipping values\n", cs);
>>> + sam9_smc_clip_cs_configuration(&cfg);
>>> + dump_timing(smc, &cfg);
>>> + }
>>> +#ifdef DEBUG
>>> + else
>>> + dump_timing(smc, &cfg);
>>> +#endif
>>
>> I'm not a big fan of #ifdef blocks inside the code.
>> You could define a dummy dump_timing function if DEBUG is not defined:
>>
>> #ifdef DEBUG
>>
>>
>> static void dump_timing(struct smc_data *smc, struct sam9_smc_config
>> *config)
>> {
>> /* your implementation */
>> }
>>
>> #else
>>
>> static inline void dump_timing(struct smc_data *smc, struct sam9_smc_config
>> *config)
>> {
>> }
>>
>> #endif
>>
>> Or just use dev_dbg when printing things in dump_timing.
>>
> I wanted to know the values when they were modified (clipped) by the
> driver. But it could be removed, knowing that clipping occurred is
> enough.
>>
>>> +
>>> + sam9_smc_cs_configure(smc->base + (0x10 * cs), &cfg);
>>> + return 0;
>>> +}
>>> +
>>> +static int smc_parse_dt(struct smc_data *smc)
>>> +{
>>> + struct device *dev = smc->dev;
>>> + const struct of_device_id *of_id = of_match_device(smc_id_table,
>>> dev);
>>> + const struct at91_smc_devtype *devtype = of_id->data;
>>> + struct device_node *child;
>>> + int ret;
>>> +
>>> + for_each_child_of_node(dev->of_node, child) {
>>> + if (!child->name)
>>> + continue;
>>> + if (!of_device_is_available(child))
>>> + continue;
>>> + ret = smc_timing_setup(smc, child, devtype);
>>> + if (ret) {
>>> + static struct property status = {
>>> + .name = "status",
>>> + .value = "disabled",
>>> + .length = sizeof("disabled"),
>>> + };
>>> + dev_err(dev, "%s set timing failed. This node will
>>> be disabled.\n",
>>> + child->full_name);
>>> + ret = of_update_property(child, &status);
>>> + if (ret < 0) {
>>> + dev_err(dev, "can't disable %s. aborting
>>> probe\n",
>>> + child->full_name);
>>> + break;
>>
>> The concept of disabling the device if timings cannot be met sounds
>> interresting...
>> Let's see what other maintainers say about this :).
>>
>>
>>> + }
>>> + }
>>> + }
>>> +
>>> + ret = of_platform_populate(dev->of_node,
>>> of_default_bus_match_table,
>>> + NULL, dev);
>>> + if (ret)
>>> + dev_err(dev, "%s fail to create devices.\n",
>>> + dev->of_node->full_name);
>>> +
>>> + return ret;
>>> +}
>>> +
>>> +static int smc_probe(struct platform_device *pdev)
>>> +{
>>> + struct resource *res;
>>> + int ret;
>>> + void __iomem *base;
>>> + struct clk *clk;
>>> + struct smc_data *smc = devm_kzalloc(&pdev->dev, sizeof(struct
>>> smc_data),
>>> + GFP_KERNEL);
>>> +
>>> + if (!smc)
>>> + return -ENOMEM;
>>> +
>>> + smc->dev = &pdev->dev;
>>> +
>>> + /* get the resource */
>>> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>>> + base = devm_request_and_ioremap(&pdev->dev, res);
>>> + if (IS_ERR(base)) {
>>> + dev_err(&pdev->dev, "can't map SMC base address\n");
>>> + return PTR_ERR(base);
>>> + }
>>> +
>>> + /* get the clock */
>>> + clk = devm_clk_get(&pdev->dev, "smc");
>>> + if (IS_ERR(clk))
>>> + return PTR_ERR(clk);
>>> +
>>> + smc->bus_clk = clk;
>>> + smc->base = base;
>>> +
>>> + /* parse the device node */
>>> + ret = smc_parse_dt(smc);
>>> + if (!ret)
>>> + dev_info(&pdev->dev, "Driver registered.\n");
>>> +
>>> + return ret;
>>> +}
>>> +
>>> +static struct platform_driver smc_driver = {
>>> + .driver = {
>>> + .name = "atmel-smc",
>>> + .owner = THIS_MODULE,
>>> + .of_match_table = smc_id_table,
>>> + },
>>> +};
>>> +module_platform_driver_probe(smc_driver, smc_probe);
>>> +
>>> +MODULE_AUTHOR("JJ Hiblot");
>>> +MODULE_DESCRIPTION("Atmel's SMC/EBI driver");
>>> +MODULE_LICENSE("GPL");
>>
>>
>> That's all for now. :)
>>
>> I'll try to test it this week end on a sama5 board.
> Thanks
>
>> Best Regards,
>>
>> Boris

2014-01-14 14:20:29

by Jean-Jacques Hiblot

[permalink] [raw]
Subject: Re: [PATCH v2 07/12] at91: dt: smc: Added smc bus driver

2014/1/11 boris brezillon <[email protected]>:
> On 09/01/2014 22:04, Jean-Jacques Hiblot wrote:
>>
>> Hi Boris,
>>
>> 2014/1/9 boris brezillon <[email protected]>:
>>>
>>> Hello JJ,
>>>
>>>
>>> On 09/01/2014 13:31, Jean-Jacques Hiblot wrote:
>>>>
>>>> The EBI/SMC external interface is used to access external peripherals
>>>> (NAND
>>>> and Ethernet controller in the case of sam9261ek). Different
>>>> configurations and
>>>> timings are required for those peripherals. This bus driver can be used
>>>> to
>>>> setup the bus timings/configuration from the device tree.
>>>> It currently accepts timings in clock period and in nanoseconds.
>>>>
>>>> Signed-off-by: Jean-Jacques Hiblot <[email protected]>
>>>> ---
>>>> drivers/memory/Kconfig | 10 ++
>>>> drivers/memory/Makefile | 1 +
>>>> drivers/memory/atmel-smc.c | 431
>>>> +++++++++++++++++++++++++++++++++++++++++++++
>>>> 3 files changed, 442 insertions(+)
>>>> create mode 100644 drivers/memory/atmel-smc.c
>>>>
>>>> diff --git a/drivers/memory/Kconfig b/drivers/memory/Kconfig
>>>> index 29a11db..fbdfd63 100644
>>>> --- a/drivers/memory/Kconfig
>>>> +++ b/drivers/memory/Kconfig
>>>> @@ -50,4 +50,14 @@ config TEGRA30_MC
>>>> analysis, especially for IOMMU/SMMU(System Memory Management
>>>> Unit) module.
>>>> +config ATMEL_SMC
>>>> + bool "Atmel SMC/EBI driver"
>>>> + default y
>>>> + depends on SOC_AT91SAM9 && OF
>>>> + help
>>>> + Driver for Atmel SMC/EBI controller.
>>>> + Used to configure the EBI (external bus interface) when the
>>>> device-
>>>> + tree is used. This bus supports NANDs, external ethernet
>>>> controller,
>>>> + SRAMs, ATA devices, etc.
>>>> +
>>>> endif
>>>> diff --git a/drivers/memory/Makefile b/drivers/memory/Makefile
>>>> index 969d923..101abc4 100644
>>>> --- a/drivers/memory/Makefile
>>>> +++ b/drivers/memory/Makefile
>>>> @@ -9,3 +9,4 @@ obj-$(CONFIG_TI_EMIF) += emif.o
>>>> obj-$(CONFIG_MVEBU_DEVBUS) += mvebu-devbus.o
>>>> obj-$(CONFIG_TEGRA20_MC) += tegra20-mc.o
>>>> obj-$(CONFIG_TEGRA30_MC) += tegra30-mc.o
>>>> +obj-$(CONFIG_ATMEL_SMC) += atmel-smc.o
>>>> diff --git a/drivers/memory/atmel-smc.c b/drivers/memory/atmel-smc.c
>>>> new file mode 100644
>>>> index 0000000..0a1d9ba
>>>> --- /dev/null
>>>> +++ b/drivers/memory/atmel-smc.c
>>>> @@ -0,0 +1,431 @@
>>>> +/*
>>>> + * EBI driver for Atmel SAM9 chips
>>>> + * inspired by the fsl weim bus driver
>>>> + *
>>>> + * Copyright (C) 2013 JJ Hiblot.
>>>> + *
>>>> + * This file is licensed under the terms of the GNU General Public
>>>> + * License version 2. This program is licensed "as is" without any
>>>> + * warranty of any kind, whether express or implied.
>>>> + */
>>>> +#include <linux/module.h>
>>>> +#include <linux/clk.h>
>>>> +#include <linux/io.h>
>>>> +#include <linux/of_device.h>
>>>> +#include <mach/at91sam9_smc.h>
>>>
>>>
>>> You should avoid machine specific headers inclusions: we're trying to get
>>> rid of them.
>>>
>>> Duplicate the code and macros you need in your driver instead.
>>
>> Is this the right way?
>
> Not necessarily. But in any case you should not reference machine specific
> headers in new drivers, because the ARM architecture maintainers are
> trying to get all architecture specific code moved into regular subsystems.
>
> There surely is a lot of pros to this approach (I'll let others detail
> these).
> The main one I see is that the subsystem maintainer is then able to track
> common designs in all available drivers and provide a common framework
> in order to :
> 1) ease other drivers development
> 2) avoid code duplication
>
> In your case, this leaves 2 solutions:
> 1) move the sam9_smc fonctions in the new driver and move the sam9_smc
> into include/linux/memory (which apparently does not exist).
> 2) copy all the functions and definition you need in your driver in order to
> get
> rid of the old implementation, and choose which one to compile using
> Kconfig
> options.
>
> If you think the sam9_smc existing implementation already match your new
> driver
> needs, I strongly recommend choosing solution 1, as it will help smoothly
> move to your
> new driver without having to modify already existing drivers using sam9_smc
> functions
> (except for the new header path ;)).
>
I agree. Merging the code of arch/arm/mach-at91/sam9_smc.c into this
new driver makes sense.
Nicolas, Jean-Christophe, what is the stand point of the atmel's
maintainers on this ?

>
>> We usually try to avoid duplication.
>
>
>
>
>>
>>>
>>>> +
>>>> +struct smc_data {
>>>> + struct clk *bus_clk;
>>>> + void __iomem *base;
>>>> + struct device *dev;
>>>> +};
>>>> +
>>>> +struct at91_smc_devtype {
>>>> + unsigned int cs_count;
>>>> +};
>>>> +
>>>> +static const struct at91_smc_devtype sam9261_smc_devtype = {
>>>> + .cs_count = 6,
>>>> +};
>>>> +
>>>> +static const struct of_device_id smc_id_table[] = {
>>>> + { .compatible = "atmel,at91sam9261-smc", .data =
>>>> &sam9261_smc_devtype},
>>>> + { }
>>>> +};
>>>> +MODULE_DEVICE_TABLE(of, smc_id_table);
>>>> +
>>>> +struct smc_parameters_type {
>>>> + const char *name;
>>>> + u16 width;
>>>> + u16 shift;
>>>> +};
>>>> +
>>>> +static const struct smc_parameters_type smc_parameters[] = {
>>>> + {"smc,burst_size", 2, 28},
>>>> + {"smc,burst_enabled", 1, 24},
>>>> + {"smc,tdf_mode", 1, 20},
>>>> + {"smc,bus_width", 2, 12},
>>>> + {"smc,byte_access_type", 1, 8},
>>>> + {"smc,nwait_mode", 2, 4},
>>>> + {"smc,write_mode", 1, 0},
>>>> + {"smc,read_mode", 1, 1},
>>>> + {NULL}
>>>> +};
>>>> +
>>>> +static int get_mode_register_from_dt(struct smc_data *smc,
>>>> + struct device_node *np,
>>>> + struct sam9_smc_config *cfg)
>>>> +{
>>>> + int ret;
>>>> + u32 val;
>>>> + struct device *dev = smc->dev;
>>>> + const struct smc_parameters_type *p = smc_parameters;
>>>> + u32 mode = cfg->mode;
>>>> +
>>>> + while (p->name) {
>>>> + ret = of_property_read_u32(np, p->name , &val);
>>>> + if (ret == -EINVAL) {
>>>> + dev_dbg(dev, "%s: property %s not set.\n",
>>>> np->name,
>>>> + p->name);
>>>> + p++;
>>>> + continue;
>>>> + } else if (ret) {
>>>> + dev_err(dev, "%s: can't get property %s.\n",
>>>> np->name,
>>>> + p->name);
>>>> + return ret;
>>>> + }
>>>> + if (val >= (1<<p->width)) {
>>>> + dev_err(dev, "%s: property %s out of range.\n",
>>>> + np->name, p->name);
>>>> + return -ERANGE;
>>>> + }
>>>> + mode &= ~(((1<<p->width)-1) << p->shift);
>>>> + mode |= (val << p->shift);
>>>> + p++;
>>>> + }
>>>> + cfg->mode = mode;
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int generic_timing_from_dt(struct smc_data *smc, struct
>>>> device_node *np,
>>>> + struct sam9_smc_config *cfg)
>>>> +{
>>>> + u32 val;
>>>> +
>>>> + if (!of_property_read_u32(np, "smc,ncs_read_setup" , &val))
>>>> + cfg->ncs_read_setup = val;
>>>> +
>>>> + if (!of_property_read_u32(np, "smc,nrd_setup" , &val))
>>>> + cfg->nrd_setup = val;
>>>> +
>>>> + if (!of_property_read_u32(np, "smc,ncs_write_setup" , &val))
>>>> + cfg->ncs_write_setup = val;
>>>> +
>>>> + if (!of_property_read_u32(np, "smc,nwe_setup" , &val))
>>>> + cfg->nwe_setup = val;
>>>> +
>>>> + if (!of_property_read_u32(np, "smc,ncs_read_pulse" , &val))
>>>> + cfg->ncs_read_pulse = val;
>>>> +
>>>> + if (!of_property_read_u32(np, "smc,nrd_pulse" , &val))
>>>> + cfg->nrd_pulse = val;
>>>> +
>>>> + if (!of_property_read_u32(np, "smc,ncs_write_pulse" , &val))
>>>> + cfg->ncs_write_pulse = val;
>>>> +
>>>> + if (!of_property_read_u32(np, "smc,nwe_pulse" , &val))
>>>> + cfg->nwe_pulse = val;
>>>> +
>>>> + if (!of_property_read_u32(np, "smc,read_cycle" , &val))
>>>> + cfg->read_cycle = val;
>>>> +
>>>> + if (!of_property_read_u32(np, "smc,write_cycle" , &val))
>>>> + cfg->write_cycle = val;
>>>> +
>>>> + if (!of_property_read_u32(np, "smc,tdf_cycles" , &val))
>>>> + cfg->tdf_cycles = val;
>>>> +
>>>> + return get_mode_register_from_dt(smc, np, cfg);
>>>> +}
>>>> +
>>>> +/* convert the time in ns in a number of clock cycles */
>>>> +static u32 ns_to_cycles(u32 ns, u32 clk)
>>>> +{
>>>> + /*
>>>> + * convert the clk to kHz for the rest of the calculation to
>>>> avoid
>>>> + * overflow
>>>> + */
>>>> + u32 clk_kHz = clk / 1000;
>>>> + u32 ncycles = ((ns * clk_kHz) + 1000000 - 1) / 1000000;
>>>
>>> What about using an u64 type and do_div ?
>>
>> easier and faster (though it's not the point here) this way, and kHz
>> ist not so imprecise :-)
>>
>>>> + return ncycles;
>>>> +}
>>>> +
>>>> +static u32 cycles_to_coded_cycle(u32 cycles, int a, int b)
>>>> +{
>>>> + u32 mask_high = (1 << a) - 1;
>>>> + u32 mask_low = (1 << b) - 1;
>>>> + u32 coded;
>>>> +
>>>> + /* check if the value can be described with the coded format */
>>>> + if (cycles & (mask_high & ~mask_low)) {
>>>> + /* not representable. we need to round up */
>>>> + cycles |= mask_high;
>>>> + cycles += 1;
>>>> + }
>>>> + /* Now the value can be represented in the coded format */
>>>> + coded = (cycles & ~mask_high) >> (a - b);
>>>> + coded |= (cycles & mask_low);
>>>> + return coded;
>>>> +}
>>>> +
>>>> +static u32 ns_to_rw_cycles(u32 ns, u32 clk)
>>>> +{
>>>> + return cycles_to_coded_cycle(ns_to_cycles(ns, clk), 8, 7);
>>>> +}
>>>> +
>>>> +static u32 ns_to_pulse_cycles(u32 ns, u32 clk)
>>>> +{
>>>> + return cycles_to_coded_cycle(ns_to_cycles(ns, clk), 8, 6);
>>>> +}
>>>> +
>>>> +static u32 ns_to_setup_cycles(u32 ns, u32 clk)
>>>> +{
>>>> + return cycles_to_coded_cycle(ns_to_cycles(ns, clk), 7, 5);
>>>> +}
>>>> +
>>>> +static u32 cycles_to_ns(u32 cycles, u32 clk)
>>>> +{
>>>> + /*
>>>> + * convert the clk to kHz for the rest of the calculation to
>>>> avoid
>>>> + * overflow
>>>> + */
>>>> + u32 clk_kHz = clk / 1000;
>>>
>>>
>>> Ditto (u64 + do_div).
>>>
>>>> + return (cycles * 1000000) / clk_kHz;
>>>> +}
>>>> +
>>>> +static u32 coded_cycle_to_cycles(u32 coded, int a, int b)
>>>> +{
>>>> + u32 cycles = (coded >> b) << a;
>>>> + u32 mask_low = (1 << b) - 1;
>>>> + cycles |= (coded & mask_low);
>>>> + return cycles;
>>>> +}
>>>> +
>>>> +static u32 rw_cycles_to_ns(u32 reg, u32 clk)
>>>> +{
>>>> + return cycles_to_ns(coded_cycle_to_cycles(reg, 8, 7), clk);
>>>> +}
>>>> +
>>>> +static u32 pulse_cycles_to_ns(u32 reg, u32 clk)
>>>> +{
>>>> + return cycles_to_ns(coded_cycle_to_cycles(reg, 8, 6), clk);
>>>> +}
>>>> +
>>>> +static u32 setup_cycles_to_ns(u32 reg, u32 clk)
>>>> +{
>>>> + return cycles_to_ns(coded_cycle_to_cycles(reg, 7, 5), clk);
>>>> +}
>>>> +
>>>> +static void dump_timing(struct smc_data *smc, struct sam9_smc_config
>>>> *config)
>>>> +{
>>>> + u32 freq = clk_get_rate(smc->bus_clk);
>>>> + struct device *dev = smc->dev;
>>>> +
>>>> +#define DUMP(fn, y) dev_info(dev, "%s : 0x%x (%d ns)\n", #y,
>>>> config->y,\
>>>> + fn(config->y, freq))
>>>> +#define DUMP_PULSE(y) DUMP(pulse_cycles_to_ns, y)
>>>> +#define DUMP_RWCYCLE(y) DUMP(rw_cycles_to_ns, y)
>>>> +#define DUMP_SETUP(y) DUMP(setup_cycles_to_ns, y)
>>>> +#define DUMP_SIMPLE(y) DUMP(cycles_to_ns, y)
>>>> +
>>>> + DUMP_SETUP(nwe_setup);
>>>> + DUMP_SETUP(ncs_write_setup);
>>>> + DUMP_SETUP(nrd_setup);
>>>> + DUMP_SETUP(ncs_read_setup);
>>>> + DUMP_PULSE(nwe_pulse);
>>>> + DUMP_PULSE(ncs_write_pulse);
>>>> + DUMP_PULSE(nrd_pulse);
>>>> + DUMP_PULSE(ncs_read_pulse);
>>>> + DUMP_RWCYCLE(write_cycle);
>>>> + DUMP_RWCYCLE(read_cycle);
>>>> + DUMP_SIMPLE(tdf_cycles);
>>>> +}
>>>> +
>>>> +static int ns_timing_from_dt(struct smc_data *smc, struct device_node
>>>> *np,
>>>> + struct sam9_smc_config *cfg)
>>>> +{
>>>> + u32 t_ns;
>>>> + u32 freq = clk_get_rate(smc->bus_clk);
>>>> +
>>>> + if (!of_property_read_u32(np, "smc,ncs_read_setup" , &t_ns))
>>>> + cfg->ncs_read_setup = ns_to_setup_cycles(t_ns, freq);
>>>> +
>>>> + if (!of_property_read_u32(np, "smc,nrd_setup" , &t_ns))
>>>> + cfg->nrd_setup = ns_to_setup_cycles(t_ns, freq);
>>>> +
>>>> + if (!of_property_read_u32(np, "smc,ncs_write_setup" , &t_ns))
>>>> + cfg->ncs_write_setup = ns_to_setup_cycles(t_ns, freq);
>>>> +
>>>> + if (!of_property_read_u32(np, "smc,nwe_setup" , &t_ns))
>>>> + cfg->nwe_setup = ns_to_setup_cycles(t_ns, freq);
>>>> +
>>>> + if (!of_property_read_u32(np, "smc,ncs_read_pulse" , &t_ns))
>>>> + cfg->ncs_read_pulse = ns_to_pulse_cycles(t_ns, freq);
>>>> +
>>>> + if (!of_property_read_u32(np, "smc,nrd_pulse" , &t_ns))
>>>> + cfg->nrd_pulse = ns_to_pulse_cycles(t_ns, freq);
>>>> +
>>>> + if (!of_property_read_u32(np, "smc,ncs_write_pulse" , &t_ns))
>>>> + cfg->ncs_write_pulse = ns_to_pulse_cycles(t_ns, freq);
>>>> +
>>>> + if (!of_property_read_u32(np, "smc,nwe_pulse" , &t_ns))
>>>> + cfg->nwe_pulse = ns_to_pulse_cycles(t_ns, freq);
>>>> +
>>>> + if (!of_property_read_u32(np, "smc,read_cycle" , &t_ns))
>>>> + cfg->read_cycle = ns_to_rw_cycles(t_ns, freq);
>>>> +
>>>> + if (!of_property_read_u32(np, "smc,write_cycle" , &t_ns))
>>>> + cfg->write_cycle = ns_to_rw_cycles(t_ns, freq);
>>>> +
>>>> + if (!of_property_read_u32(np, "smc,tdf_cycles" , &t_ns))
>>>> + cfg->tdf_cycles = ns_to_cycles(t_ns, freq);
>>>> +
>>>> + return get_mode_register_from_dt(smc, np, cfg);
>>>> +}
>>>> +
>>>> +struct converter {
>>>> + const char *name;
>>>> + int (*fn) (struct smc_data *smc, struct device_node *np,
>>>> + struct sam9_smc_config *cfg);
>>>> +};
>>>> +static const struct converter converters[] = {
>>>> + {"raw", generic_timing_from_dt},
>>>> + {"nanosec", ns_timing_from_dt},
>>>> +};
>>>
>>>
>>> Could you use more specific names like:
>>> "atmel,smc-converter-generic"
>>> "atmel,smc-converter-nand"
>>> ...
>>
>> Isn't it a bit redudant? smc,converter = "atmel,smc-converter-generic";
>>
>>> IMHO the timing unit should be specified in the property names:
>>> smc,ncs_read_setup-ns
>>> smc,ncs_read_setup-cycles
>>>
>> True. Although cycles is misleading. It's more a raw register value.
>> For pulse, setup and rw cycle, the register value is not identical to
>> the number of cycles.
>>>
>>>
>>>
>>>> +
>>>> +/* Parse and set the timing for this device. */
>>>> +static int smc_timing_setup(struct smc_data *smc, struct device_node
>>>> *np,
>>>> + const struct at91_smc_devtype *devtype)
>>>> +{
>>>> + int ret;
>>>> + u32 cs;
>>>> + int i;
>>>> + struct device *dev = smc->dev;
>>>> + const struct converter *converter;
>>>> + const char *converter_name = NULL;
>>>> + struct sam9_smc_config cfg;
>>>> +
>>>> + ret = of_property_read_u32(np, "smc,cs" , &cs);
>>>
>>>
>>> Shouldn't this be stored in the reg property ?
>>> After all, in your DM9000 patch you use "@cs,offset" to identify the
>>> node...
>>
>> True
>>
>>>
>>>> + if (ret < 0) {
>>>> + dev_err(dev, "missing mandatory property : smc,cs\n");
>>>> + return ret;
>>>> + }
>>>> + if (cs >= devtype->cs_count) {
>>>> + dev_err(dev, "invalid value for property smc,cs (=%d)."
>>>> + "Must be in range 0 to %d\n", cs, devtype->cs_count-1);
>>>> + return -EINVAL;
>>>> + }
>>>> +
>>>> + of_property_read_string(np, "smc,converter", &converter_name);
>>>
>>>
>>> What about using the "compatible" property + struct of_device_id instead
>>> of
>>> "smc,converter" property + struct converter ?
>>
>> Because one instance of the driver handles several chip selects and
>> each may use a different converter.
>>
>>>
>>>> + if (converter_name) {
>>>> + for (i = 0; i < ARRAY_SIZE(converters); i++)
>>>> + if (strcmp(converters[i].name, converter_name)
>>>> ==
>>>> 0)
>>>> + converter = &converters[i];
>>>> + if (!converter) {
>>>> + dev_info(dev, "unknown converter. aborting\n");
>>>> + return -EINVAL;
>>>> + }
>>>> + } else {
>>>> + dev_dbg(dev, "cs %d: no smc converter provided. using "
>>>> + "raw register values\n", cs);
>>>> + converter = &converters[0];
>>>> + }
>>>> + dev_dbg(dev, "cs %d using converter : %s\n", cs,
>>>> converter->name);
>>>> + sam9_smc_cs_read(smc->base + (0x10 * cs), &cfg);
>>>> + converter->fn(smc, np, &cfg);
>>>> + ret = sam9_smc_check_cs_configuration(&cfg);
>>>> + if (ret < 0) {
>>>> + dev_info(dev, "invalid smc configuration for cs %d."
>>>> + "clipping values\n", cs);
>>>> + sam9_smc_clip_cs_configuration(&cfg);
>>>> + dump_timing(smc, &cfg);
>>>> + }
>>>> +#ifdef DEBUG
>>>> + else
>>>> + dump_timing(smc, &cfg);
>>>> +#endif
>>>
>>>
>>> I'm not a big fan of #ifdef blocks inside the code.
>>> You could define a dummy dump_timing function if DEBUG is not defined:
>>>
>>> #ifdef DEBUG
>>>
>>>
>>> static void dump_timing(struct smc_data *smc, struct sam9_smc_config
>>> *config)
>>> {
>>> /* your implementation */
>>> }
>>>
>>> #else
>>>
>>> static inline void dump_timing(struct smc_data *smc, struct
>>> sam9_smc_config
>>> *config)
>>> {
>>> }
>>>
>>> #endif
>>>
>>> Or just use dev_dbg when printing things in dump_timing.
>>>
>> I wanted to know the values when they were modified (clipped) by the
>> driver. But it could be removed, knowing that clipping occurred is
>> enough.
>>>
>>>
>>>> +
>>>> + sam9_smc_cs_configure(smc->base + (0x10 * cs), &cfg);
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int smc_parse_dt(struct smc_data *smc)
>>>> +{
>>>> + struct device *dev = smc->dev;
>>>> + const struct of_device_id *of_id = of_match_device(smc_id_table,
>>>> dev);
>>>> + const struct at91_smc_devtype *devtype = of_id->data;
>>>> + struct device_node *child;
>>>> + int ret;
>>>> +
>>>> + for_each_child_of_node(dev->of_node, child) {
>>>> + if (!child->name)
>>>> + continue;
>>>> + if (!of_device_is_available(child))
>>>> + continue;
>>>> + ret = smc_timing_setup(smc, child, devtype);
>>>> + if (ret) {
>>>> + static struct property status = {
>>>> + .name = "status",
>>>> + .value = "disabled",
>>>> + .length = sizeof("disabled"),
>>>> + };
>>>> + dev_err(dev, "%s set timing failed. This node
>>>> will
>>>> be disabled.\n",
>>>> + child->full_name);
>>>> + ret = of_update_property(child, &status);
>>>> + if (ret < 0) {
>>>> + dev_err(dev, "can't disable %s. aborting
>>>> probe\n",
>>>> + child->full_name);
>>>> + break;
>>>
>>>
>>> The concept of disabling the device if timings cannot be met sounds
>>> interresting...
>>> Let's see what other maintainers say about this :).
>>>
>>>
>>>> + }
>>>> + }
>>>> + }
>>>> +
>>>> + ret = of_platform_populate(dev->of_node,
>>>> of_default_bus_match_table,
>>>> + NULL, dev);
>>>> + if (ret)
>>>> + dev_err(dev, "%s fail to create devices.\n",
>>>> + dev->of_node->full_name);
>>>> +
>>>> + return ret;
>>>> +}
>>>> +
>>>> +static int smc_probe(struct platform_device *pdev)
>>>> +{
>>>> + struct resource *res;
>>>> + int ret;
>>>> + void __iomem *base;
>>>> + struct clk *clk;
>>>> + struct smc_data *smc = devm_kzalloc(&pdev->dev, sizeof(struct
>>>> smc_data),
>>>> + GFP_KERNEL);
>>>> +
>>>> + if (!smc)
>>>> + return -ENOMEM;
>>>> +
>>>> + smc->dev = &pdev->dev;
>>>> +
>>>> + /* get the resource */
>>>> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>>>> + base = devm_request_and_ioremap(&pdev->dev, res);
>>>> + if (IS_ERR(base)) {
>>>> + dev_err(&pdev->dev, "can't map SMC base address\n");
>>>> + return PTR_ERR(base);
>>>> + }
>>>> +
>>>> + /* get the clock */
>>>> + clk = devm_clk_get(&pdev->dev, "smc");
>>>> + if (IS_ERR(clk))
>>>> + return PTR_ERR(clk);
>>>> +
>>>> + smc->bus_clk = clk;
>>>> + smc->base = base;
>>>> +
>>>> + /* parse the device node */
>>>> + ret = smc_parse_dt(smc);
>>>> + if (!ret)
>>>> + dev_info(&pdev->dev, "Driver registered.\n");
>>>> +
>>>> + return ret;
>>>> +}
>>>> +
>>>> +static struct platform_driver smc_driver = {
>>>> + .driver = {
>>>> + .name = "atmel-smc",
>>>> + .owner = THIS_MODULE,
>>>> + .of_match_table = smc_id_table,
>>>> + },
>>>> +};
>>>> +module_platform_driver_probe(smc_driver, smc_probe);
>>>> +
>>>> +MODULE_AUTHOR("JJ Hiblot");
>>>> +MODULE_DESCRIPTION("Atmel's SMC/EBI driver");
>>>> +MODULE_LICENSE("GPL");
>>>
>>>
>>>
>>> That's all for now. :)
>>>
>>> I'll try to test it this week end on a sama5 board.
>>
>> Thanks
>>
>>> Best Regards,
>>>
>>> Boris
>
>

2014-01-14 15:01:39

by Nicolas Ferre

[permalink] [raw]
Subject: Re: [PATCH v2 07/12] at91: dt: smc: Added smc bus driver

On 14/01/2014 15:20, Jean-Jacques Hiblot :
> 2014/1/11 boris brezillon <[email protected]>:
>> On 09/01/2014 22:04, Jean-Jacques Hiblot wrote:
>>>
>>> Hi Boris,
>>>
>>> 2014/1/9 boris brezillon <[email protected]>:
>>>>
>>>> Hello JJ,
>>>>
>>>>
>>>> On 09/01/2014 13:31, Jean-Jacques Hiblot wrote:
>>>>>
>>>>> The EBI/SMC external interface is used to access external peripherals
>>>>> (NAND
>>>>> and Ethernet controller in the case of sam9261ek). Different
>>>>> configurations and
>>>>> timings are required for those peripherals. This bus driver can be used
>>>>> to
>>>>> setup the bus timings/configuration from the device tree.
>>>>> It currently accepts timings in clock period and in nanoseconds.
>>>>>
>>>>> Signed-off-by: Jean-Jacques Hiblot <[email protected]>
>>>>> ---
>>>>> drivers/memory/Kconfig | 10 ++
>>>>> drivers/memory/Makefile | 1 +
>>>>> drivers/memory/atmel-smc.c | 431
>>>>> +++++++++++++++++++++++++++++++++++++++++++++
>>>>> 3 files changed, 442 insertions(+)
>>>>> create mode 100644 drivers/memory/atmel-smc.c
>>>>>
>>>>> diff --git a/drivers/memory/Kconfig b/drivers/memory/Kconfig
>>>>> index 29a11db..fbdfd63 100644
>>>>> --- a/drivers/memory/Kconfig
>>>>> +++ b/drivers/memory/Kconfig
>>>>> @@ -50,4 +50,14 @@ config TEGRA30_MC
>>>>> analysis, especially for IOMMU/SMMU(System Memory Management
>>>>> Unit) module.
>>>>> +config ATMEL_SMC
>>>>> + bool "Atmel SMC/EBI driver"
>>>>> + default y
>>>>> + depends on SOC_AT91SAM9 && OF
>>>>> + help
>>>>> + Driver for Atmel SMC/EBI controller.
>>>>> + Used to configure the EBI (external bus interface) when the
>>>>> device-
>>>>> + tree is used. This bus supports NANDs, external ethernet
>>>>> controller,
>>>>> + SRAMs, ATA devices, etc.
>>>>> +
>>>>> endif
>>>>> diff --git a/drivers/memory/Makefile b/drivers/memory/Makefile
>>>>> index 969d923..101abc4 100644
>>>>> --- a/drivers/memory/Makefile
>>>>> +++ b/drivers/memory/Makefile
>>>>> @@ -9,3 +9,4 @@ obj-$(CONFIG_TI_EMIF) += emif.o
>>>>> obj-$(CONFIG_MVEBU_DEVBUS) += mvebu-devbus.o
>>>>> obj-$(CONFIG_TEGRA20_MC) += tegra20-mc.o
>>>>> obj-$(CONFIG_TEGRA30_MC) += tegra30-mc.o
>>>>> +obj-$(CONFIG_ATMEL_SMC) += atmel-smc.o
>>>>> diff --git a/drivers/memory/atmel-smc.c b/drivers/memory/atmel-smc.c
>>>>> new file mode 100644
>>>>> index 0000000..0a1d9ba
>>>>> --- /dev/null
>>>>> +++ b/drivers/memory/atmel-smc.c
>>>>> @@ -0,0 +1,431 @@
>>>>> +/*
>>>>> + * EBI driver for Atmel SAM9 chips
>>>>> + * inspired by the fsl weim bus driver
>>>>> + *
>>>>> + * Copyright (C) 2013 JJ Hiblot.
>>>>> + *
>>>>> + * This file is licensed under the terms of the GNU General Public
>>>>> + * License version 2. This program is licensed "as is" without any
>>>>> + * warranty of any kind, whether express or implied.
>>>>> + */
>>>>> +#include <linux/module.h>
>>>>> +#include <linux/clk.h>
>>>>> +#include <linux/io.h>
>>>>> +#include <linux/of_device.h>
>>>>> +#include <mach/at91sam9_smc.h>
>>>>
>>>>
>>>> You should avoid machine specific headers inclusions: we're trying to get
>>>> rid of them.
>>>>
>>>> Duplicate the code and macros you need in your driver instead.
>>>
>>> Is this the right way?
>>
>> Not necessarily. But in any case you should not reference machine specific
>> headers in new drivers, because the ARM architecture maintainers are
>> trying to get all architecture specific code moved into regular subsystems.
>>
>> There surely is a lot of pros to this approach (I'll let others detail
>> these).
>> The main one I see is that the subsystem maintainer is then able to track
>> common designs in all available drivers and provide a common framework
>> in order to :
>> 1) ease other drivers development
>> 2) avoid code duplication
>>
>> In your case, this leaves 2 solutions:
>> 1) move the sam9_smc fonctions in the new driver and move the sam9_smc
>> into include/linux/memory (which apparently does not exist).
>> 2) copy all the functions and definition you need in your driver in order to
>> get
>> rid of the old implementation, and choose which one to compile using
>> Kconfig
>> options.
>>
>> If you think the sam9_smc existing implementation already match your new
>> driver
>> needs, I strongly recommend choosing solution 1, as it will help smoothly
>> move to your
>> new driver without having to modify already existing drivers using sam9_smc
>> functions
>> (except for the new header path ;)).
>>
> I agree. Merging the code of arch/arm/mach-at91/sam9_smc.c into this
> new driver makes sense.
> Nicolas, Jean-Christophe, what is the stand point of the atmel's
> maintainers on this ?

I do agree with solution 1.
We will need to pay attention to the path modification on other files
but the benefit of having a SMC driver and the move of code out of the
mach-at91 directory definitively worth it.

Thanks for you work Jean-Jacques.

Bye,

>>> We usually try to avoid duplication.
>>
>>>
>>>>
>>>>> +
>>>>> +struct smc_data {
>>>>> + struct clk *bus_clk;
>>>>> + void __iomem *base;
>>>>> + struct device *dev;
>>>>> +};
>>>>> +
>>>>> +struct at91_smc_devtype {
>>>>> + unsigned int cs_count;
>>>>> +};
>>>>> +
>>>>> +static const struct at91_smc_devtype sam9261_smc_devtype = {
>>>>> + .cs_count = 6,
>>>>> +};
>>>>> +
>>>>> +static const struct of_device_id smc_id_table[] = {
>>>>> + { .compatible = "atmel,at91sam9261-smc", .data =
>>>>> &sam9261_smc_devtype},
>>>>> + { }
>>>>> +};
>>>>> +MODULE_DEVICE_TABLE(of, smc_id_table);
>>>>> +
>>>>> +struct smc_parameters_type {
>>>>> + const char *name;
>>>>> + u16 width;
>>>>> + u16 shift;
>>>>> +};
>>>>> +
>>>>> +static const struct smc_parameters_type smc_parameters[] = {
>>>>> + {"smc,burst_size", 2, 28},
>>>>> + {"smc,burst_enabled", 1, 24},
>>>>> + {"smc,tdf_mode", 1, 20},
>>>>> + {"smc,bus_width", 2, 12},
>>>>> + {"smc,byte_access_type", 1, 8},
>>>>> + {"smc,nwait_mode", 2, 4},
>>>>> + {"smc,write_mode", 1, 0},
>>>>> + {"smc,read_mode", 1, 1},
>>>>> + {NULL}
>>>>> +};
>>>>> +
>>>>> +static int get_mode_register_from_dt(struct smc_data *smc,
>>>>> + struct device_node *np,
>>>>> + struct sam9_smc_config *cfg)
>>>>> +{
>>>>> + int ret;
>>>>> + u32 val;
>>>>> + struct device *dev = smc->dev;
>>>>> + const struct smc_parameters_type *p = smc_parameters;
>>>>> + u32 mode = cfg->mode;
>>>>> +
>>>>> + while (p->name) {
>>>>> + ret = of_property_read_u32(np, p->name , &val);
>>>>> + if (ret == -EINVAL) {
>>>>> + dev_dbg(dev, "%s: property %s not set.\n",
>>>>> np->name,
>>>>> + p->name);
>>>>> + p++;
>>>>> + continue;
>>>>> + } else if (ret) {
>>>>> + dev_err(dev, "%s: can't get property %s.\n",
>>>>> np->name,
>>>>> + p->name);
>>>>> + return ret;
>>>>> + }
>>>>> + if (val >= (1<<p->width)) {
>>>>> + dev_err(dev, "%s: property %s out of range.\n",
>>>>> + np->name, p->name);
>>>>> + return -ERANGE;
>>>>> + }
>>>>> + mode &= ~(((1<<p->width)-1) << p->shift);
>>>>> + mode |= (val << p->shift);
>>>>> + p++;
>>>>> + }
>>>>> + cfg->mode = mode;
>>>>> + return 0;
>>>>> +}
>>>>> +
>>>>> +static int generic_timing_from_dt(struct smc_data *smc, struct
>>>>> device_node *np,
>>>>> + struct sam9_smc_config *cfg)
>>>>> +{
>>>>> + u32 val;
>>>>> +
>>>>> + if (!of_property_read_u32(np, "smc,ncs_read_setup" , &val))
>>>>> + cfg->ncs_read_setup = val;
>>>>> +
>>>>> + if (!of_property_read_u32(np, "smc,nrd_setup" , &val))
>>>>> + cfg->nrd_setup = val;
>>>>> +
>>>>> + if (!of_property_read_u32(np, "smc,ncs_write_setup" , &val))
>>>>> + cfg->ncs_write_setup = val;
>>>>> +
>>>>> + if (!of_property_read_u32(np, "smc,nwe_setup" , &val))
>>>>> + cfg->nwe_setup = val;
>>>>> +
>>>>> + if (!of_property_read_u32(np, "smc,ncs_read_pulse" , &val))
>>>>> + cfg->ncs_read_pulse = val;
>>>>> +
>>>>> + if (!of_property_read_u32(np, "smc,nrd_pulse" , &val))
>>>>> + cfg->nrd_pulse = val;
>>>>> +
>>>>> + if (!of_property_read_u32(np, "smc,ncs_write_pulse" , &val))
>>>>> + cfg->ncs_write_pulse = val;
>>>>> +
>>>>> + if (!of_property_read_u32(np, "smc,nwe_pulse" , &val))
>>>>> + cfg->nwe_pulse = val;
>>>>> +
>>>>> + if (!of_property_read_u32(np, "smc,read_cycle" , &val))
>>>>> + cfg->read_cycle = val;
>>>>> +
>>>>> + if (!of_property_read_u32(np, "smc,write_cycle" , &val))
>>>>> + cfg->write_cycle = val;
>>>>> +
>>>>> + if (!of_property_read_u32(np, "smc,tdf_cycles" , &val))
>>>>> + cfg->tdf_cycles = val;
>>>>> +
>>>>> + return get_mode_register_from_dt(smc, np, cfg);
>>>>> +}
>>>>> +
>>>>> +/* convert the time in ns in a number of clock cycles */
>>>>> +static u32 ns_to_cycles(u32 ns, u32 clk)
>>>>> +{
>>>>> + /*
>>>>> + * convert the clk to kHz for the rest of the calculation to
>>>>> avoid
>>>>> + * overflow
>>>>> + */
>>>>> + u32 clk_kHz = clk / 1000;
>>>>> + u32 ncycles = ((ns * clk_kHz) + 1000000 - 1) / 1000000;
>>>>
>>>> What about using an u64 type and do_div ?
>>>
>>> easier and faster (though it's not the point here) this way, and kHz
>>> ist not so imprecise :-)
>>>
>>>>> + return ncycles;
>>>>> +}
>>>>> +
>>>>> +static u32 cycles_to_coded_cycle(u32 cycles, int a, int b)
>>>>> +{
>>>>> + u32 mask_high = (1 << a) - 1;
>>>>> + u32 mask_low = (1 << b) - 1;
>>>>> + u32 coded;
>>>>> +
>>>>> + /* check if the value can be described with the coded format */
>>>>> + if (cycles & (mask_high & ~mask_low)) {
>>>>> + /* not representable. we need to round up */
>>>>> + cycles |= mask_high;
>>>>> + cycles += 1;
>>>>> + }
>>>>> + /* Now the value can be represented in the coded format */
>>>>> + coded = (cycles & ~mask_high) >> (a - b);
>>>>> + coded |= (cycles & mask_low);
>>>>> + return coded;
>>>>> +}
>>>>> +
>>>>> +static u32 ns_to_rw_cycles(u32 ns, u32 clk)
>>>>> +{
>>>>> + return cycles_to_coded_cycle(ns_to_cycles(ns, clk), 8, 7);
>>>>> +}
>>>>> +
>>>>> +static u32 ns_to_pulse_cycles(u32 ns, u32 clk)
>>>>> +{
>>>>> + return cycles_to_coded_cycle(ns_to_cycles(ns, clk), 8, 6);
>>>>> +}
>>>>> +
>>>>> +static u32 ns_to_setup_cycles(u32 ns, u32 clk)
>>>>> +{
>>>>> + return cycles_to_coded_cycle(ns_to_cycles(ns, clk), 7, 5);
>>>>> +}
>>>>> +
>>>>> +static u32 cycles_to_ns(u32 cycles, u32 clk)
>>>>> +{
>>>>> + /*
>>>>> + * convert the clk to kHz for the rest of the calculation to
>>>>> avoid
>>>>> + * overflow
>>>>> + */
>>>>> + u32 clk_kHz = clk / 1000;
>>>>
>>>>
>>>> Ditto (u64 + do_div).
>>>>
>>>>> + return (cycles * 1000000) / clk_kHz;
>>>>> +}
>>>>> +
>>>>> +static u32 coded_cycle_to_cycles(u32 coded, int a, int b)
>>>>> +{
>>>>> + u32 cycles = (coded >> b) << a;
>>>>> + u32 mask_low = (1 << b) - 1;
>>>>> + cycles |= (coded & mask_low);
>>>>> + return cycles;
>>>>> +}
>>>>> +
>>>>> +static u32 rw_cycles_to_ns(u32 reg, u32 clk)
>>>>> +{
>>>>> + return cycles_to_ns(coded_cycle_to_cycles(reg, 8, 7), clk);
>>>>> +}
>>>>> +
>>>>> +static u32 pulse_cycles_to_ns(u32 reg, u32 clk)
>>>>> +{
>>>>> + return cycles_to_ns(coded_cycle_to_cycles(reg, 8, 6), clk);
>>>>> +}
>>>>> +
>>>>> +static u32 setup_cycles_to_ns(u32 reg, u32 clk)
>>>>> +{
>>>>> + return cycles_to_ns(coded_cycle_to_cycles(reg, 7, 5), clk);
>>>>> +}
>>>>> +
>>>>> +static void dump_timing(struct smc_data *smc, struct sam9_smc_config
>>>>> *config)
>>>>> +{
>>>>> + u32 freq = clk_get_rate(smc->bus_clk);
>>>>> + struct device *dev = smc->dev;
>>>>> +
>>>>> +#define DUMP(fn, y) dev_info(dev, "%s : 0x%x (%d ns)\n", #y,
>>>>> config->y,\
>>>>> + fn(config->y, freq))
>>>>> +#define DUMP_PULSE(y) DUMP(pulse_cycles_to_ns, y)
>>>>> +#define DUMP_RWCYCLE(y) DUMP(rw_cycles_to_ns, y)
>>>>> +#define DUMP_SETUP(y) DUMP(setup_cycles_to_ns, y)
>>>>> +#define DUMP_SIMPLE(y) DUMP(cycles_to_ns, y)
>>>>> +
>>>>> + DUMP_SETUP(nwe_setup);
>>>>> + DUMP_SETUP(ncs_write_setup);
>>>>> + DUMP_SETUP(nrd_setup);
>>>>> + DUMP_SETUP(ncs_read_setup);
>>>>> + DUMP_PULSE(nwe_pulse);
>>>>> + DUMP_PULSE(ncs_write_pulse);
>>>>> + DUMP_PULSE(nrd_pulse);
>>>>> + DUMP_PULSE(ncs_read_pulse);
>>>>> + DUMP_RWCYCLE(write_cycle);
>>>>> + DUMP_RWCYCLE(read_cycle);
>>>>> + DUMP_SIMPLE(tdf_cycles);
>>>>> +}
>>>>> +
>>>>> +static int ns_timing_from_dt(struct smc_data *smc, struct device_node
>>>>> *np,
>>>>> + struct sam9_smc_config *cfg)
>>>>> +{
>>>>> + u32 t_ns;
>>>>> + u32 freq = clk_get_rate(smc->bus_clk);
>>>>> +
>>>>> + if (!of_property_read_u32(np, "smc,ncs_read_setup" , &t_ns))
>>>>> + cfg->ncs_read_setup = ns_to_setup_cycles(t_ns, freq);
>>>>> +
>>>>> + if (!of_property_read_u32(np, "smc,nrd_setup" , &t_ns))
>>>>> + cfg->nrd_setup = ns_to_setup_cycles(t_ns, freq);
>>>>> +
>>>>> + if (!of_property_read_u32(np, "smc,ncs_write_setup" , &t_ns))
>>>>> + cfg->ncs_write_setup = ns_to_setup_cycles(t_ns, freq);
>>>>> +
>>>>> + if (!of_property_read_u32(np, "smc,nwe_setup" , &t_ns))
>>>>> + cfg->nwe_setup = ns_to_setup_cycles(t_ns, freq);
>>>>> +
>>>>> + if (!of_property_read_u32(np, "smc,ncs_read_pulse" , &t_ns))
>>>>> + cfg->ncs_read_pulse = ns_to_pulse_cycles(t_ns, freq);
>>>>> +
>>>>> + if (!of_property_read_u32(np, "smc,nrd_pulse" , &t_ns))
>>>>> + cfg->nrd_pulse = ns_to_pulse_cycles(t_ns, freq);
>>>>> +
>>>>> + if (!of_property_read_u32(np, "smc,ncs_write_pulse" , &t_ns))
>>>>> + cfg->ncs_write_pulse = ns_to_pulse_cycles(t_ns, freq);
>>>>> +
>>>>> + if (!of_property_read_u32(np, "smc,nwe_pulse" , &t_ns))
>>>>> + cfg->nwe_pulse = ns_to_pulse_cycles(t_ns, freq);
>>>>> +
>>>>> + if (!of_property_read_u32(np, "smc,read_cycle" , &t_ns))
>>>>> + cfg->read_cycle = ns_to_rw_cycles(t_ns, freq);
>>>>> +
>>>>> + if (!of_property_read_u32(np, "smc,write_cycle" , &t_ns))
>>>>> + cfg->write_cycle = ns_to_rw_cycles(t_ns, freq);
>>>>> +
>>>>> + if (!of_property_read_u32(np, "smc,tdf_cycles" , &t_ns))
>>>>> + cfg->tdf_cycles = ns_to_cycles(t_ns, freq);
>>>>> +
>>>>> + return get_mode_register_from_dt(smc, np, cfg);
>>>>> +}
>>>>> +
>>>>> +struct converter {
>>>>> + const char *name;
>>>>> + int (*fn) (struct smc_data *smc, struct device_node *np,
>>>>> + struct sam9_smc_config *cfg);
>>>>> +};
>>>>> +static const struct converter converters[] = {
>>>>> + {"raw", generic_timing_from_dt},
>>>>> + {"nanosec", ns_timing_from_dt},
>>>>> +};
>>>>
>>>>
>>>> Could you use more specific names like:
>>>> "atmel,smc-converter-generic"
>>>> "atmel,smc-converter-nand"
>>>> ...
>>>
>>> Isn't it a bit redudant? smc,converter = "atmel,smc-converter-generic";
>>>
>>>> IMHO the timing unit should be specified in the property names:
>>>> smc,ncs_read_setup-ns
>>>> smc,ncs_read_setup-cycles
>>>>
>>> True. Although cycles is misleading. It's more a raw register value.
>>> For pulse, setup and rw cycle, the register value is not identical to
>>> the number of cycles.
>>>>
>>>>
>>>>
>>>>> +
>>>>> +/* Parse and set the timing for this device. */
>>>>> +static int smc_timing_setup(struct smc_data *smc, struct device_node
>>>>> *np,
>>>>> + const struct at91_smc_devtype *devtype)
>>>>> +{
>>>>> + int ret;
>>>>> + u32 cs;
>>>>> + int i;
>>>>> + struct device *dev = smc->dev;
>>>>> + const struct converter *converter;
>>>>> + const char *converter_name = NULL;
>>>>> + struct sam9_smc_config cfg;
>>>>> +
>>>>> + ret = of_property_read_u32(np, "smc,cs" , &cs);
>>>>
>>>>
>>>> Shouldn't this be stored in the reg property ?
>>>> After all, in your DM9000 patch you use "@cs,offset" to identify the
>>>> node...
>>>
>>> True
>>>
>>>>
>>>>> + if (ret < 0) {
>>>>> + dev_err(dev, "missing mandatory property : smc,cs\n");
>>>>> + return ret;
>>>>> + }
>>>>> + if (cs >= devtype->cs_count) {
>>>>> + dev_err(dev, "invalid value for property smc,cs (=%d)."
>>>>> + "Must be in range 0 to %d\n", cs, devtype->cs_count-1);
>>>>> + return -EINVAL;
>>>>> + }
>>>>> +
>>>>> + of_property_read_string(np, "smc,converter", &converter_name);
>>>>
>>>>
>>>> What about using the "compatible" property + struct of_device_id instead
>>>> of
>>>> "smc,converter" property + struct converter ?
>>>
>>> Because one instance of the driver handles several chip selects and
>>> each may use a different converter.
>>>
>>>>
>>>>> + if (converter_name) {
>>>>> + for (i = 0; i < ARRAY_SIZE(converters); i++)
>>>>> + if (strcmp(converters[i].name, converter_name)
>>>>> ==
>>>>> 0)
>>>>> + converter = &converters[i];
>>>>> + if (!converter) {
>>>>> + dev_info(dev, "unknown converter. aborting\n");
>>>>> + return -EINVAL;
>>>>> + }
>>>>> + } else {
>>>>> + dev_dbg(dev, "cs %d: no smc converter provided. using "
>>>>> + "raw register values\n", cs);
>>>>> + converter = &converters[0];
>>>>> + }
>>>>> + dev_dbg(dev, "cs %d using converter : %s\n", cs,
>>>>> converter->name);
>>>>> + sam9_smc_cs_read(smc->base + (0x10 * cs), &cfg);
>>>>> + converter->fn(smc, np, &cfg);
>>>>> + ret = sam9_smc_check_cs_configuration(&cfg);
>>>>> + if (ret < 0) {
>>>>> + dev_info(dev, "invalid smc configuration for cs %d."
>>>>> + "clipping values\n", cs);
>>>>> + sam9_smc_clip_cs_configuration(&cfg);
>>>>> + dump_timing(smc, &cfg);
>>>>> + }
>>>>> +#ifdef DEBUG
>>>>> + else
>>>>> + dump_timing(smc, &cfg);
>>>>> +#endif
>>>>
>>>>
>>>> I'm not a big fan of #ifdef blocks inside the code.
>>>> You could define a dummy dump_timing function if DEBUG is not defined:
>>>>
>>>> #ifdef DEBUG
>>>>
>>>>
>>>> static void dump_timing(struct smc_data *smc, struct sam9_smc_config
>>>> *config)
>>>> {
>>>> /* your implementation */
>>>> }
>>>>
>>>> #else
>>>>
>>>> static inline void dump_timing(struct smc_data *smc, struct
>>>> sam9_smc_config
>>>> *config)
>>>> {
>>>> }
>>>>
>>>> #endif
>>>>
>>>> Or just use dev_dbg when printing things in dump_timing.
>>>>
>>> I wanted to know the values when they were modified (clipped) by the
>>> driver. But it could be removed, knowing that clipping occurred is
>>> enough.
>>>>
>>>>
>>>>> +
>>>>> + sam9_smc_cs_configure(smc->base + (0x10 * cs), &cfg);
>>>>> + return 0;
>>>>> +}
>>>>> +
>>>>> +static int smc_parse_dt(struct smc_data *smc)
>>>>> +{
>>>>> + struct device *dev = smc->dev;
>>>>> + const struct of_device_id *of_id = of_match_device(smc_id_table,
>>>>> dev);
>>>>> + const struct at91_smc_devtype *devtype = of_id->data;
>>>>> + struct device_node *child;
>>>>> + int ret;
>>>>> +
>>>>> + for_each_child_of_node(dev->of_node, child) {
>>>>> + if (!child->name)
>>>>> + continue;
>>>>> + if (!of_device_is_available(child))
>>>>> + continue;
>>>>> + ret = smc_timing_setup(smc, child, devtype);
>>>>> + if (ret) {
>>>>> + static struct property status = {
>>>>> + .name = "status",
>>>>> + .value = "disabled",
>>>>> + .length = sizeof("disabled"),
>>>>> + };
>>>>> + dev_err(dev, "%s set timing failed. This node
>>>>> will
>>>>> be disabled.\n",
>>>>> + child->full_name);
>>>>> + ret = of_update_property(child, &status);
>>>>> + if (ret < 0) {
>>>>> + dev_err(dev, "can't disable %s. aborting
>>>>> probe\n",
>>>>> + child->full_name);
>>>>> + break;
>>>>
>>>>
>>>> The concept of disabling the device if timings cannot be met sounds
>>>> interresting...
>>>> Let's see what other maintainers say about this :).
>>>>
>>>>
>>>>> + }
>>>>> + }
>>>>> + }
>>>>> +
>>>>> + ret = of_platform_populate(dev->of_node,
>>>>> of_default_bus_match_table,
>>>>> + NULL, dev);
>>>>> + if (ret)
>>>>> + dev_err(dev, "%s fail to create devices.\n",
>>>>> + dev->of_node->full_name);
>>>>> +
>>>>> + return ret;
>>>>> +}
>>>>> +
>>>>> +static int smc_probe(struct platform_device *pdev)
>>>>> +{
>>>>> + struct resource *res;
>>>>> + int ret;
>>>>> + void __iomem *base;
>>>>> + struct clk *clk;
>>>>> + struct smc_data *smc = devm_kzalloc(&pdev->dev, sizeof(struct
>>>>> smc_data),
>>>>> + GFP_KERNEL);
>>>>> +
>>>>> + if (!smc)
>>>>> + return -ENOMEM;
>>>>> +
>>>>> + smc->dev = &pdev->dev;
>>>>> +
>>>>> + /* get the resource */
>>>>> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>>>>> + base = devm_request_and_ioremap(&pdev->dev, res);
>>>>> + if (IS_ERR(base)) {
>>>>> + dev_err(&pdev->dev, "can't map SMC base address\n");
>>>>> + return PTR_ERR(base);
>>>>> + }
>>>>> +
>>>>> + /* get the clock */
>>>>> + clk = devm_clk_get(&pdev->dev, "smc");
>>>>> + if (IS_ERR(clk))
>>>>> + return PTR_ERR(clk);
>>>>> +
>>>>> + smc->bus_clk = clk;
>>>>> + smc->base = base;
>>>>> +
>>>>> + /* parse the device node */
>>>>> + ret = smc_parse_dt(smc);
>>>>> + if (!ret)
>>>>> + dev_info(&pdev->dev, "Driver registered.\n");
>>>>> +
>>>>> + return ret;
>>>>> +}
>>>>> +
>>>>> +static struct platform_driver smc_driver = {
>>>>> + .driver = {
>>>>> + .name = "atmel-smc",
>>>>> + .owner = THIS_MODULE,
>>>>> + .of_match_table = smc_id_table,
>>>>> + },
>>>>> +};
>>>>> +module_platform_driver_probe(smc_driver, smc_probe);
>>>>> +
>>>>> +MODULE_AUTHOR("JJ Hiblot");
>>>>> +MODULE_DESCRIPTION("Atmel's SMC/EBI driver");
>>>>> +MODULE_LICENSE("GPL");
>>>>
>>>>
>>>>
>>>> That's all for now. :)
>>>>
>>>> I'll try to test it this week end on a sama5 board.
>>>
>>> Thanks
>>>
>>>> Best Regards,
>>>>
>>>> Boris
>>
>>
>
>


--
Nicolas Ferre

2014-01-14 16:54:17

by Nicolas Ferre

[permalink] [raw]
Subject: Re: [PATCH v2 00/12] Device Tree support for the at91sam9261ek

On 09/01/2014 13:31, Jean-Jacques Hiblot :
> This patch set aims at bringing a basic device tree support for the sam9261.
> It's mostly based on the sam9263 stuff.

Nice! Thanks a lot for stepping up.

One general comment though: I would like to have new AT91 SoC described
in DT integrating mainline with Common Clock Framework implemented.
I feel that it would make sense to directly convert sam9261 to CCF to
avoid the pain of an intermediary DT description that would last only
for a couple of kernel revisions.

> It introduces a new driver for the smc/ebi bus. It's used to configure the EBI
> from the DT. I haven't documented its DT bindings yet. Timings can be provided
> as raw values or nanoseconds.

Thanks a lot for taking this SMC-as-a-driver task: it is very cool to
have it!

I will try to review your patches in the coming days... But Boris
already did a good job at this ;-)

> Change since V1:
> * changed the DT representation to use address translation and separate the
> timings' configuration from the device properties by adding a "simple-bus"
> inetrmediate node.
> * moved the smc driver from drivers/bus to drivers/memmory
> * smc driver now accepts timings in nanoseconds as well as raw register values
> * smc driver can clip the timings if they're out of bound and dump them to the
> console
> * DM9000 timings are now described in nanosecs (for the virtue of example)
>
> supported features:
> * dbgu
> * nand
> * lcd
> * ethernet
> * leds
>
> Jean-Jacques
>
> Jean-Jacques Hiblot (12):
> at91: dt: Add at91sam9261 dt SoC support
> at91: dt: sam9261: Basic Device Tree support for the at91sam9261ek
> at91: dt: sam9261: Added support for the lcd display
> at91: smc: export sam9_smc_cs_read and sam9_smc_cs_configure.
> at91: smc: Increased the size of tdf_cycles in struct sam9_smc_config.
> at91: smc: Adds helper functions to validate and clip the smc timings.
> at91: dt: smc: Added smc bus driver
> at91: sam9261: Add a clock definition for the smc
> at91: dt: sam9261: Pinmux DT entries for the SMC/EBI interface
> at91: dt: sam9261: Add an entry in the DT for the SMC/EBI bus driver.
> at91: dt: sam9261: moved the NAND under the smc node
> at91: dt: sam9261: Added DM9000 in the device tree
>
> arch/arm/boot/dts/Makefile | 2 +
> arch/arm/boot/dts/at91sam9261.dtsi | 639 +++++++++++++++++++++++++
> arch/arm/boot/dts/at91sam9261ek.dts | 164 +++++++
> arch/arm/mach-at91/at91sam9261.c | 17 +
> arch/arm/mach-at91/include/mach/at91sam9_smc.h | 6 +-
> arch/arm/mach-at91/sam9_smc.c | 81 +++-
> drivers/memory/Kconfig | 10 +
> drivers/memory/Makefile | 1 +
> drivers/memory/atmel-smc.c | 431 +++++++++++++++++
> 9 files changed, 1348 insertions(+), 3 deletions(-)
> create mode 100644 arch/arm/boot/dts/at91sam9261.dtsi
> create mode 100644 arch/arm/boot/dts/at91sam9261ek.dts
> create mode 100644 drivers/memory/atmel-smc.c
>


--
Nicolas Ferre

2014-01-14 17:01:24

by Nicolas Ferre

[permalink] [raw]
Subject: Re: [PATCH v2 01/12] at91: dt: Add at91sam9261 dt SoC support

On 09/01/2014 13:31, Jean-Jacques Hiblot :
> This patch adds the basics to support the Device Tree on a sam9261-based platform
>
> Signed-off-by: Jean-Jacques Hiblot <[email protected]>
> ---
> arch/arm/boot/dts/at91sam9261.dtsi | 476 +++++++++++++++++++++++++++++++++++++
> arch/arm/mach-at91/at91sam9261.c | 15 ++
> 2 files changed, 491 insertions(+)
> create mode 100644 arch/arm/boot/dts/at91sam9261.dtsi
>
> diff --git a/arch/arm/boot/dts/at91sam9261.dtsi b/arch/arm/boot/dts/at91sam9261.dtsi
> new file mode 100644
> index 0000000..773c3d6
> --- /dev/null
> +++ b/arch/arm/boot/dts/at91sam9261.dtsi
> @@ -0,0 +1,476 @@
> +/*
> + * at91sam9261.dtsi - Device Tree Include file for AT91SAM9261 SoC
> + *
> + * Copyright (C) 2013 Jean-Jacques Hiblot <[email protected]>
> + *
> + * Licensed under GPLv2 only.
> + */
> +
> +#include "skeleton.dtsi"
> +#include <dt-bindings/pinctrl/at91.h>
> +#include <dt-bindings/interrupt-controller/irq.h>
> +#include <dt-bindings/gpio/gpio.h>
> +
> +/ {
> + model = "Atmel AT91SAM9261 family SoC";
> + compatible = "atmel,at91sam9261";
> + interrupt-parent = <&aic>;
> +
> + aliases {
> + serial0 = &dbgu;
> + serial1 = &usart0;
> + serial2 = &usart1;
> + serial3 = &usart2;
> + gpio0 = &pioA;
> + gpio1 = &pioB;
> + gpio2 = &pioC;
> + tcb0 = &tcb0;
> + i2c0 = &i2c0;
> + ssc0 = &ssc0;
> + ssc1 = &ssc1;
> + };
> + cpus {
> + #address-cells = <0>;
> + #size-cells = <0>;
> +
> + cpu {
> + compatible = "arm,arm926ej-s";
> + device_type = "cpu";
> + };
> + };
> +
> + memory {
> + reg = <0x20000000 0x08000000>;
> + };
> +
> + ahb {
> + compatible = "simple-bus";
> + #address-cells = <1>;
> + #size-cells = <1>;
> + ranges;
> +
> + apb {
> + compatible = "simple-bus";
> + #address-cells = <1>;
> + #size-cells = <1>;
> + ranges;

I know that it is not always done but can you please sort all nodes by
ascending address order? It is always simple to deal with node additions
when sorted this way.

> +
> + aic: interrupt-controller@fffff000 {
> + #interrupt-cells = <3>;
> + compatible = "atmel,at91rm9200-aic";
> + interrupt-controller;
> + reg = <0xfffff000 0x200>;
> + atmel,external-irqs = <29 30 31>;
> + };
> +
> + pmc: pmc@fffffc00 {
> + compatible = "atmel,at91rm9200-pmc";
> + reg = <0xfffffc00 0x100>;
> + };
> +
> + ramc: ramc@ffffea00 {
> + compatible = "atmel,at91sam9260-sdramc";
> + reg = <0xffffea00 0x200>;
> + };
> +
> + pit: timer@fffffd30 {
> + compatible = "atmel,at91sam9260-pit";
> + reg = <0xfffffd30 0xf>;
> + interrupts = <1 IRQ_TYPE_LEVEL_HIGH 7>;
> + };
> +
> + tcb0: timer@fffa0000 {
> + compatible = "atmel,at91rm9200-tcb";
> + reg = <0xfffa0000 0x100>;
> + interrupts = < 17 IRQ_TYPE_LEVEL_HIGH 0
> + 18 IRQ_TYPE_LEVEL_HIGH 0
> + 19 IRQ_TYPE_LEVEL_HIGH 0
> + >;
> + status = "disabled";
> + };
> +
> + rstc@fffffd00 {
> + compatible = "atmel,at91sam9260-rstc";
> + reg = <0xfffffd00 0x10>;
> + };
> +
> + shdwc@fffffd10 {
> + compatible = "atmel,at91sam9260-shdwc";
> + reg = <0xfffffd10 0x10>;
> + };
> +
> + pinctrl@fffff400 {
> + #address-cells = <1>;
> + #size-cells = <1>;
> + compatible = "atmel,at91rm9200-pinctrl", "simple-bus";
> + ranges = <0xfffff400 0xfffff400 0xa00>;
> +
> + atmel,mux-mask = <
> + /* A B */
> + 0xffffffff 0xfffffff7 /* pioA */
> + 0xffffffff 0xfffffff4 /* pioB */
> + 0xffffffff 0xffffff07 /* pioC */
> + >;
> +
> + /* shared pinctrl settings */
> + dbgu {
> + pinctrl_dbgu: dbgu-0 {
> + atmel,pins =
> + <AT91_PIOA 9 AT91_PERIPH_A AT91_PINCTRL_NONE /* PA9 periph A */

You may have to remove comments for pin descriptions. It is not needed
anymore with pre-processor macros.

> + AT91_PIOA 10 AT91_PERIPH_A AT91_PINCTRL_PULL_UP>; /* PA10 periph A with pullup */
> + };
> + };
> +
> + usart0 {
> + pinctrl_usart0: usart0-0 {
> + atmel,pins =
> + <AT91_PIOC 8 AT91_PERIPH_A AT91_PINCTRL_PULL_UP /* PC8 periph A with pullup */
> + AT91_PIOC 9 AT91_PERIPH_A AT91_PINCTRL_NONE>; /* PC9 periph A */
> + };
> +
> + pinctrl_usart0_rts: usart0_rts-0 {
> + atmel,pins =
> + <AT91_PIOC 10 AT91_PERIPH_A AT91_PINCTRL_NONE>; /* PC10 periph A */
> + };
> +
> + pinctrl_usart0_cts: usart0_cts-0 {
> + atmel,pins =
> + <AT91_PIOC 11 AT91_PERIPH_A AT91_PINCTRL_NONE>; /* PC11 periph A */
> + };
> + };
> +
> + usart1 {
> + pinctrl_usart1: usart1-0 {
> + atmel,pins =
> + <AT91_PIOC 12 AT91_PERIPH_A AT91_PINCTRL_PULL_UP /* PC12 periph A with pullup */
> + AT91_PIOC 13 AT91_PERIPH_A AT91_PINCTRL_NONE>; /* PC13 periph A */
> + };
> +
> + pinctrl_usart1_rts: usart1_rts-0 {
> + atmel,pins =
> + <AT91_PIOA 12 AT91_PERIPH_B AT91_PINCTRL_NONE>; /* PA12 periph B */
> + };
> +
> + pinctrl_usart1_cts: usart1_cts-0 {
> + atmel,pins =
> + <AT91_PIOA 13 AT91_PERIPH_B AT91_PINCTRL_NONE>; /* PA13 periph B */
> + };
> + };
> +
> + usart2 {
> + pinctrl_usart2: usart2-0 {
> + atmel,pins =
> + <AT91_PIOC 14 AT91_PERIPH_A AT91_PINCTRL_PULL_UP /* PC14 periph A with pullup */
> + AT91_PIOC 15 AT91_PERIPH_A AT91_PINCTRL_NONE>; /* PC15 periph A */
> + };
> +
> + pinctrl_usart2_rts: usart2_rts-0 {
> + atmel,pins =
> + <AT91_PIOA 15 AT91_PERIPH_B AT91_PINCTRL_NONE>; /* PA15 periph B */
> + };
> +
> + pinctrl_usart2_cts: usart2_cts-0 {
> + atmel,pins =
> + <AT91_PIOA 16 AT91_PERIPH_B AT91_PINCTRL_NONE>; /* PA16 periph B */
> + };
> + };
> +
> + nand {
> + pinctrl_nand: nand-0 {
> + atmel,pins =
> + <AT91_PIOC 15 AT91_PERIPH_GPIO AT91_PINCTRL_PULL_UP /* PC15 gpio RDY pin pull_up*/
> + AT91_PIOC 14 AT91_PERIPH_GPIO AT91_PINCTRL_PULL_UP>; /* PC14 gpio enable pin pull_up */
> + };
> + };
> +
> + mmc0 {
> + pinctrl_mmc0_clk: mmc0_clk-0 {
> + atmel,pins =
> + <AT91_PIOA 2 AT91_PERIPH_B AT91_PINCTRL_NONE>; /* PA2 periph B */
> + };
> +
> + pinctrl_mmc0_slot0_cmd_dat0: mmc0_slot0_cmd_dat0-0 {
> + atmel,pins =
> + <AT91_PIOA 1 AT91_PERIPH_B AT91_PINCTRL_PULL_UP /* PA1 periph B with pullup */
> + AT91_PIOA 0 AT91_PERIPH_B AT91_PINCTRL_PULL_UP>; /* PA0 periph B with pullup */
> + };
> +
> + pinctrl_mmc0_slot0_dat1_3: mmc0_slot0_dat1_3-0 {
> + atmel,pins =
> + <AT91_PIOA 4 AT91_PERIPH_B AT91_PINCTRL_PULL_UP /* PA4 periph B with pullup */
> + AT91_PIOA 5 AT91_PERIPH_B AT91_PINCTRL_PULL_UP /* PA5 periph B with pullup */
> + AT91_PIOA 6 AT91_PERIPH_B AT91_PINCTRL_PULL_UP>; /* PA6 periph B with pullup */
> + };
> + };
> +
> + ssc0 {
> + pinctrl_ssc0_tx: ssc0_tx-0 {
> + atmel,pins =
> + <AT91_PIOB 21 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB21 periph A */
> + AT91_PIOB 22 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB22 periph A */
> + AT91_PIOB 23 AT91_PERIPH_A AT91_PINCTRL_NONE>; /* PB23 periph A */
> + };
> +
> + pinctrl_ssc0_rx: ssc0_rx-0 {
> + atmel,pins =
> + <AT91_PIOB 24 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB24 periph A */
> + AT91_PIOB 25 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB25 periph A */
> + AT91_PIOB 26 AT91_PERIPH_A AT91_PINCTRL_NONE>; /* PB26 periph A */
> + };
> + };
> +
> + ssc1 {
> + pinctrl_ssc1_tx: ssc1_tx-0 {
> + atmel,pins =
> + <AT91_PIOA 17 AT91_PERIPH_B AT91_PINCTRL_NONE /* PA17 periph B */
> + AT91_PIOA 18 AT91_PERIPH_B AT91_PINCTRL_NONE /* PA18 periph B */
> + AT91_PIOA 19 AT91_PERIPH_B AT91_PINCTRL_NONE>; /* PA19 periph B */
> + };
> +
> + pinctrl_ssc1_rx: ssc1_rx-0 {
> + atmel,pins =
> + <AT91_PIOA 20 AT91_PERIPH_B AT91_PINCTRL_NONE /* PA20 periph B */
> + AT91_PIOA 21 AT91_PERIPH_B AT91_PINCTRL_NONE /* PA21 periph B */
> + AT91_PIOA 22 AT91_PERIPH_B AT91_PINCTRL_NONE>; /* PA22 periph B */
> + };
> + };
> +
> + spi0 {
> + pinctrl_spi0: spi0-0 {
> + atmel,pins =
> + <AT91_PIOA 0 AT91_PERIPH_A AT91_PINCTRL_NONE /* PA0 periph A SPI0_MISO pin */
> + AT91_PIOA 1 AT91_PERIPH_A AT91_PINCTRL_NONE /* PA1 periph A SPI0_MOSI pin */
> + AT91_PIOA 2 AT91_PERIPH_A AT91_PINCTRL_NONE>; /* PA2 periph A SPI0_SPCK pin */
> + };
> + };
> +
> + spi1 {
> + pinctrl_spi1: spi1-0 {
> + atmel,pins =
> + <AT91_PIOB 30 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB30 periph A SPI1_MISO pin */
> + AT91_PIOB 31 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB31 periph A SPI1_MOSI pin */
> + AT91_PIOB 29 AT91_PERIPH_A AT91_PINCTRL_NONE>; /* PB29 periph A SPI1_SPCK pin */
> + };
> + };
> +
> + tcb0 {
> + pinctrl_tcb0_tclk0: tcb0_tclk0-0 {
> + atmel,pins = <AT91_PIOC 16 AT91_PERIPH_B AT91_PINCTRL_NONE>;
> + };
> +
> + pinctrl_tcb0_tclk1: tcb0_tclk1-0 {
> + atmel,pins = <AT91_PIOC 17 AT91_PERIPH_B AT91_PINCTRL_NONE>;
> + };
> +
> + pinctrl_tcb0_tclk2: tcb0_tclk2-0 {
> + atmel,pins = <AT91_PIOC 18 AT91_PERIPH_B AT91_PINCTRL_NONE>;
> + };
> +
> + pinctrl_tcb0_tioa0: tcb0_tioa0-0 {
> + atmel,pins = <AT91_PIOC 19 AT91_PERIPH_B AT91_PINCTRL_NONE>;
> + };
> +
> + pinctrl_tcb0_tioa1: tcb0_tioa1-0 {
> + atmel,pins = <AT91_PIOC 21 AT91_PERIPH_B AT91_PINCTRL_NONE>;
> + };
> +
> + pinctrl_tcb0_tioa2: tcb0_tioa2-0 {
> + atmel,pins = <AT91_PIOC 23 AT91_PERIPH_B AT91_PINCTRL_NONE>;
> + };
> +
> + pinctrl_tcb0_tiob0: tcb0_tiob0-0 {
> + atmel,pins = <AT91_PIOC 20 AT91_PERIPH_B AT91_PINCTRL_NONE>;
> + };
> +
> + pinctrl_tcb0_tiob1: tcb0_tiob1-0 {
> + atmel,pins = <AT91_PIOC 22 AT91_PERIPH_B AT91_PINCTRL_NONE>;
> + };
> +
> + pinctrl_tcb0_tiob2: tcb0_tiob2-0 {
> + atmel,pins = <AT91_PIOC 24 AT91_PERIPH_B AT91_PINCTRL_NONE>;
> + };
> + };
> +
> + pioA: gpio@fffff400 {
> + compatible = "atmel,at91rm9200-gpio";
> + reg = <0xfffff400 0x200>;
> + interrupts = <2 IRQ_TYPE_LEVEL_HIGH 1>;
> + #gpio-cells = <2>;
> + gpio-controller;
> + interrupt-controller;
> + #interrupt-cells = <2>;
> + };
> +
> + pioB: gpio@fffff600 {
> + compatible = "atmel,at91rm9200-gpio";
> + reg = <0xfffff600 0x200>;
> + interrupts = <3 IRQ_TYPE_LEVEL_HIGH 1>;
> + #gpio-cells = <2>;
> + gpio-controller;
> + interrupt-controller;
> + #interrupt-cells = <2>;
> + };
> +
> + pioC: gpio@fffff800 {
> + compatible = "atmel,at91rm9200-gpio";
> + reg = <0xfffff800 0x200>;
> + interrupts = <4 IRQ_TYPE_LEVEL_HIGH 1>;
> + #gpio-cells = <2>;
> + gpio-controller;
> + interrupt-controller;
> + #interrupt-cells = <2>;
> + };
> + };
> +
> + dbgu: serial@fffff200 {
> + compatible = "atmel,at91sam9260-usart";
> + reg = <0xfffff200 0x200>;
> + interrupts = <1 IRQ_TYPE_LEVEL_HIGH 7>;
> + pinctrl-names = "default";
> + pinctrl-0 = <&pinctrl_dbgu>;
> + status = "disabled";
> + };
> +
> + usart0: serial@fffb0000 {
> + compatible = "atmel,at91sam9260-usart";
> + reg = <0xfffb0000 0x200>;
> + interrupts = <6 IRQ_TYPE_LEVEL_HIGH 5>;
> + atmel,use-dma-rx;
> + atmel,use-dma-tx;
> + pinctrl-names = "default";
> + pinctrl-0 = <&pinctrl_usart0>;
> + status = "disabled";
> + };
> +
> + usart1: serial@ffffb400 {
> + compatible = "atmel,at91sam9260-usart";
> + reg = <0xfffb4000 0x200>;
> + interrupts = <7 IRQ_TYPE_LEVEL_HIGH 5>;
> + atmel,use-dma-rx;
> + atmel,use-dma-tx;
> + pinctrl-names = "default";
> + pinctrl-0 = <&pinctrl_usart1>;
> + status = "disabled";
> + };
> +
> + usart2: serial@fff94000 {
> + compatible = "atmel,at91sam9260-usart";
> + reg = <0xfffb8000 0x200>;
> + interrupts = <8 IRQ_TYPE_LEVEL_HIGH 5>;
> + atmel,use-dma-rx;
> + atmel,use-dma-tx;
> + pinctrl-names = "default";
> + pinctrl-0 = <&pinctrl_usart2>;
> + status = "disabled";
> + };
> +
> + ssc0: ssc@fffbc000 {
> + compatible = "atmel,at91rm9200-ssc";
> + reg = <0xfffbc000 0x4000>;
> + interrupts = <14 IRQ_TYPE_LEVEL_HIGH 5>;
> + pinctrl-names = "default";
> + pinctrl-0 = <&pinctrl_ssc0_tx &pinctrl_ssc0_rx>;
> + status = "disabled";
> + };
> +
> + ssc1: ssc@fffc0000 {
> + compatible = "atmel,at91rm9200-ssc";
> + reg = <0xfffc0000 0x4000>;
> + interrupts = <15 IRQ_TYPE_LEVEL_HIGH 5>;
> + pinctrl-names = "default";
> + pinctrl-0 = <&pinctrl_ssc1_tx &pinctrl_ssc1_rx>;
> + status = "disabled";
> + };
> +
> + usb1: gadget@fffa4000 {
> + compatible = "atmel,at91rm9200-udc";
> + reg = <0xfffa4000 0x4000>;
> + interrupts = <10 IRQ_TYPE_LEVEL_HIGH 2>;
> + status = "disabled";
> + };
> +
> + i2c0: i2c@fffac000 {
> + compatible = "atmel,at91sam9261-i2c";

isn't it "atmel,at91sam9260-i2c" ?

> + reg = <0xfffac000 0x100>;
> + interrupts = <11 IRQ_TYPE_LEVEL_HIGH 6>;
> + #address-cells = <1>;
> + #size-cells = <0>;
> + status = "disabled";
> + };
> +
> + mmc0: mmc@fffa8000 {
> + compatible = "atmel,hsmci";
> + reg = <0xfffa8000 0x600>;
> + interrupts = <9 IRQ_TYPE_LEVEL_HIGH 0>;
> + #address-cells = <1>;
> + #size-cells = <0>;
> + status = "disabled";
> + };
> +
> + watchdog@fffffd40 {
> + compatible = "atmel,at91sam9260-wdt";
> + reg = <0xfffffd40 0x10>;
> + status = "disabled";
> + };
> +
> + spi0: spi@fffc8000 {
> + #address-cells = <1>;
> + #size-cells = <0>;
> + compatible = "atmel,at91rm9200-spi";
> + reg = <0xfffc8000 0x200>;
> + interrupts = <12 IRQ_TYPE_LEVEL_HIGH 3>;
> + pinctrl-names = "default";
> + pinctrl-0 = <&pinctrl_spi0>;
> + status = "disabled";
> + };
> +
> + spi1: spi@fffcc000 {
> + #address-cells = <1>;
> + #size-cells = <0>;
> + compatible = "atmel,at91rm9200-spi";
> + reg = <0xfffcc000 0x200>;
> + interrupts = <13 IRQ_TYPE_LEVEL_HIGH 3>;
> + pinctrl-names = "default";
> + pinctrl-0 = <&pinctrl_spi1>;
> + status = "disabled";
> + };
> + };
> +
> + nand0: nand@40000000 {
> + compatible = "atmel,at91rm9200-nand";
> + #address-cells = <1>;
> + #size-cells = <1>;
> + reg = <0x40000000 0x10000000>;
> + atmel,nand-addr-offset = <22>;
> + atmel,nand-cmd-offset = <21>;
> + pinctrl-names = "default";
> + pinctrl-0 = <&pinctrl_nand>;
> +
> + gpios = <&pioC 15 GPIO_ACTIVE_HIGH
> + &pioC 14 GPIO_ACTIVE_HIGH
> + 0
> + >;
> + status = "disabled";
> + };
> +
> + usb0: ohci@00500000 {
> + compatible = "atmel,at91rm9200-ohci", "usb-ohci";
> + reg = <0x00500000 0x100000>;
> + interrupts = <20 IRQ_TYPE_LEVEL_HIGH 2>;
> + status = "disabled";
> + };
> + };
> +
> + i2c@0 {
> + compatible = "i2c-gpio";
> + gpios = <&pioA 7 GPIO_ACTIVE_HIGH /* sda */
> + &pioA 8 GPIO_ACTIVE_HIGH /* scl */
> + >;
> + i2c-gpio,sda-open-drain;
> + i2c-gpio,scl-open-drain;
> + i2c-gpio,delay-us = <2>; /* ~100 kHz */
> + #address-cells = <1>;
> + #size-cells = <0>;
> + status = "disabled";
> + };
> +};
> diff --git a/arch/arm/mach-at91/at91sam9261.c b/arch/arm/mach-at91/at91sam9261.c
> index 6276b4c..200d17a 100644
> --- a/arch/arm/mach-at91/at91sam9261.c
> +++ b/arch/arm/mach-at91/at91sam9261.c
> @@ -189,6 +189,21 @@ static struct clk_lookup periph_clocks_lookups[] = {
> CLKDEV_CON_ID("pioA", &pioA_clk),
> CLKDEV_CON_ID("pioB", &pioB_clk),
> CLKDEV_CON_ID("pioC", &pioC_clk),
> + /* more usart lookup table for DT entries */
> + CLKDEV_CON_DEV_ID("usart", "fffff200.serial", &mck),
> + CLKDEV_CON_DEV_ID("usart", "fffb0000.serial", &usart0_clk),
> + CLKDEV_CON_DEV_ID("usart", "ffffb400.serial", &usart1_clk),
> + CLKDEV_CON_DEV_ID("usart", "fff94000.serial", &usart2_clk),
> + /* more tc lookup table for DT entries */
> + CLKDEV_CON_DEV_ID("t0_clk", "fffa0000.timer", &tc0_clk),
> + CLKDEV_CON_DEV_ID("hclk", "500000.ohci", &ohci_clk),
> + CLKDEV_CON_DEV_ID("spi_clk", "fffc8000.spi", &spi0_clk),
> + CLKDEV_CON_DEV_ID("spi_clk", "fffcc000.spi", &spi1_clk),
> + CLKDEV_CON_DEV_ID("mci_clk", "fffa8000.mmc", &mmc_clk),
> + CLKDEV_CON_DEV_ID(NULL, "fffac000.i2c", &twi_clk),
> + CLKDEV_CON_DEV_ID(NULL, "fffff400.gpio", &pioA_clk),
> + CLKDEV_CON_DEV_ID(NULL, "fffff600.gpio", &pioB_clk),
> + CLKDEV_CON_DEV_ID(NULL, "fffff800.gpio", &pioC_clk),

Yes, this is where I would like the CCF to be implemented...


> };
>
> static struct clk_lookup usart_clocks_lookups[] = {
>


--
Nicolas Ferre

2014-01-14 17:06:39

by Nicolas Ferre

[permalink] [raw]
Subject: Re: [PATCH v2 02/12] at91: dt: sam9261: Basic Device Tree support for the at91sam9261ek

On 09/01/2014 13:31, Jean-Jacques Hiblot :
> This patch implements a simple DTS to boot a at91sam9261ek with a dt-enabled
> kernel (at91_dt_defconfig).
> Only dbgu, nand and watchdog are described in the DT.
>
> Signed-off-by: Jean-Jacques Hiblot <[email protected]>
> ---
> arch/arm/boot/dts/Makefile | 2 +
> arch/arm/boot/dts/at91sam9261ek.dts | 75 +++++++++++++++++++++++++++++++++++++
> 2 files changed, 77 insertions(+)
> create mode 100644 arch/arm/boot/dts/at91sam9261ek.dts
>
> diff --git a/arch/arm/boot/dts/Makefile b/arch/arm/boot/dts/Makefile
> index 772a30e..ece523d 100644
> --- a/arch/arm/boot/dts/Makefile
> +++ b/arch/arm/boot/dts/Makefile
> @@ -11,6 +11,8 @@ dtb-$(CONFIG_ARCH_AT91) += ethernut5.dtb
> dtb-$(CONFIG_ARCH_AT91) += evk-pro3.dtb
> dtb-$(CONFIG_ARCH_AT91) += tny_a9260.dtb
> dtb-$(CONFIG_ARCH_AT91) += usb_a9260.dtb
> +# sam9261
> +dtb-$(CONFIG_ARCH_AT91) += at91sam9261ek.dtb
> # sam9263
> dtb-$(CONFIG_ARCH_AT91) += at91sam9263ek.dtb
> dtb-$(CONFIG_ARCH_AT91) += tny_a9263.dtb
> diff --git a/arch/arm/boot/dts/at91sam9261ek.dts b/arch/arm/boot/dts/at91sam9261ek.dts
> new file mode 100644
> index 0000000..f3d22a9
> --- /dev/null
> +++ b/arch/arm/boot/dts/at91sam9261ek.dts
> @@ -0,0 +1,75 @@
> +/*
> + * at91sam9261ek.dts - Device Tree file for Atmel at91sam9261 reference board
> + *
> + * Copyright (C) 2013 Jean-Jacques Hiblot <[email protected]>
> + *
> + * Licensed under GPLv2 only.
> + */
> +/dts-v1/;
> +#include "at91sam9261.dtsi"
> +
> +/ {
> + model = "Atmel at91sam9261ek";
> + compatible = "atmel,at91sam9261ek", "atmel,at91sam9261", "atmel,at91sam9";
> +
> + chosen {
> + bootargs = "mem=64M console=ttyS0,115200";

No "mem=" argument, please.

> + };
> +
> + memory {
> + reg = <0x20000000 0x4000000>;

This is where memory is specified.

> + };
> +
> + clocks {
> + #address-cells = <1>;
> + #size-cells = <1>;
> + ranges;
> +
> + main_clock: clock@0 {
> + compatible = "atmel,osc", "fixed-clock";
> + clock-frequency = <18432000>;
> + };
> + };
> +
> + ahb {
> + apb {
> + dbgu: serial@fffff200 {
> + status = "okay";
> + };
> +
> + watchdog@fffffd40 {
> + status = "okay";
> + };
> + };
> +
> + nand0: nand@40000000 {
> + nand-bus-width = <8>;
> + nand-ecc-mode = "soft";
> + nand-on-flash-bbt = <1>;
> + status = "okay";
> + at91bootstrap@0 {
> + label = "at91bootstrap";
> + reg = <0x0 0x20000>;

Maybe more partitions are needed by default. You may need to align to
the common partition scheme the we use for sama5d3 and:

http://www.at91.com/linux4sam/bin/view/Linux4SAM/GettingStarted#Linux4SAM_NandFlash_demo_Memory

(even if it can be discussed ;-))

> + };
> + };
> + };
> +
> + leds {
> + compatible = "gpio-leds";
> + ds8 {
> + label = "ds8";
> + gpios = <&pioA 13 GPIO_ACTIVE_LOW>;
> + linux,default-trigger = "none";
> + };
> + ds7 {
> + label = "ds7";
> + gpios = <&pioA 14 GPIO_ACTIVE_LOW>;
> + linux,default-trigger = "nand-disk";
> + };
> + ds1 {
> + label = "ds1";
> + gpios = <&pioA 23 GPIO_ACTIVE_LOW>;
> + linux,default-trigger = "heartbeat";
> + };
> + };
> +};
>


--
Nicolas Ferre

2014-01-14 17:09:22

by Nicolas Ferre

[permalink] [raw]
Subject: Re: [PATCH v2 03/12] at91: dt: sam9261: Added support for the lcd display

On 09/01/2014 13:31, Jean-Jacques Hiblot :
> Signed-off-by: Jean-Jacques Hiblot <[email protected]>
> ---
> arch/arm/boot/dts/at91sam9261.dtsi | 37 ++++++++++++++++++++++++++++++++++++-
> arch/arm/boot/dts/at91sam9261ek.dts | 31 +++++++++++++++++++++++++++++++
> arch/arm/mach-at91/at91sam9261.c | 1 +
> 3 files changed, 68 insertions(+), 1 deletion(-)
>
> diff --git a/arch/arm/boot/dts/at91sam9261.dtsi b/arch/arm/boot/dts/at91sam9261.dtsi
> index 773c3d6..cd219b9 100644
> --- a/arch/arm/boot/dts/at91sam9261.dtsi
> +++ b/arch/arm/boot/dts/at91sam9261.dtsi
> @@ -290,7 +290,33 @@
> atmel,pins = <AT91_PIOC 24 AT91_PERIPH_B AT91_PINCTRL_NONE>;
> };
> };
> -
> + fb {
> + pinctrl_fb: fb-0 {
> + atmel,pins =
> + <AT91_PIOB 1 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB1 periph A */

Ditto: remove comments.

> + AT91_PIOB 2 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB2 periph A */
> + AT91_PIOB 3 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB3 periph A */
> + AT91_PIOB 7 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB7 periph A */
> + AT91_PIOB 8 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB8 periph A */
> + AT91_PIOB 9 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB9 periph A */
> + AT91_PIOB 10 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB10 periph A */
> + AT91_PIOB 11 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB11 periph A */
> + AT91_PIOB 12 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB12 periph A */
> + AT91_PIOB 15 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB15 periph A */
> + AT91_PIOB 16 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB16 periph A */
> + AT91_PIOB 17 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB17 periph A */
> + AT91_PIOB 18 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB18 periph A */
> + AT91_PIOB 19 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB19 periph A */
> + AT91_PIOB 20 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB20 periph A */
> + AT91_PIOB 23 AT91_PERIPH_B AT91_PINCTRL_NONE /* PB23 periph B */
> + AT91_PIOB 24 AT91_PERIPH_B AT91_PINCTRL_NONE /* PB24 periph B */
> + AT91_PIOB 25 AT91_PERIPH_B AT91_PINCTRL_NONE /* PB25 periph B */
> + AT91_PIOB 26 AT91_PERIPH_B AT91_PINCTRL_NONE /* PB26 periph B */
> + AT91_PIOB 27 AT91_PERIPH_B AT91_PINCTRL_NONE /* PB27 periph B */
> + AT91_PIOB 28 AT91_PERIPH_B AT91_PINCTRL_NONE /* PB28 periph B */
> + >;
> + };
> + };
> pioA: gpio@fffff400 {
> compatible = "atmel,at91rm9200-gpio";
> reg = <0xfffff400 0x200>;
> @@ -436,6 +462,15 @@
> };
> };
>
> + fb0: fb@0x00600000 {
> + compatible = "atmel,at91sam9261-lcdc";
> + reg = <0x00600000 0x1000>;
> + interrupts = <21 IRQ_TYPE_LEVEL_HIGH 3>;
> + pinctrl-names = "default";
> + pinctrl-0 = <&pinctrl_fb>;
> + status = "disabled";
> + };
> +
> nand0: nand@40000000 {
> compatible = "atmel,at91rm9200-nand";
> #address-cells = <1>;
> diff --git a/arch/arm/boot/dts/at91sam9261ek.dts b/arch/arm/boot/dts/at91sam9261ek.dts
> index f3d22a9..03c05fc 100644
> --- a/arch/arm/boot/dts/at91sam9261ek.dts
> +++ b/arch/arm/boot/dts/at91sam9261ek.dts
> @@ -52,6 +52,37 @@
> reg = <0x0 0x20000>;
> };
> };
> +
> + fb0: fb@0x00600000 {
> + display = <&display0>;
> + status = "okay";

Nitpicking: Status is usually the last entry of a node (just before
sub-nodes): it is good to follow this habit.

> + atmel,power-control-gpio = <&pioA 12 GPIO_ACTIVE_LOW>;
> + display0: display {
> + bits-per-pixel = <16>;
> + atmel,lcdcon-backlight;
> + atmel,dmacon = <0x1>;
> + atmel,lcdcon2 = <0x80008002>;
> + atmel,guard-time = <1>;
> + atmel,lcd-wiring-mode = "BRG";
> +
> + display-timings {
> + native-mode = <&timing0>;
> + timing0: timing0 {
> + clock-frequency = <4965000>;
> + hactive = <240>;
> + vactive = <320>;
> + hback-porch = <1>;
> + hfront-porch = <33>;
> + vback-porch = <1>;
> + vfront-porch = <0>;
> + hsync-len = <5>;
> + vsync-len = <1>;
> + hsync-active = <1>;
> + vsync-active = <1>;
> + };
> + };
> + };
> + };
> };
>
> leds {
> diff --git a/arch/arm/mach-at91/at91sam9261.c b/arch/arm/mach-at91/at91sam9261.c
> index 200d17a..a67bfe6 100644
> --- a/arch/arm/mach-at91/at91sam9261.c
> +++ b/arch/arm/mach-at91/at91sam9261.c
> @@ -197,6 +197,7 @@ static struct clk_lookup periph_clocks_lookups[] = {
> /* more tc lookup table for DT entries */
> CLKDEV_CON_DEV_ID("t0_clk", "fffa0000.timer", &tc0_clk),
> CLKDEV_CON_DEV_ID("hclk", "500000.ohci", &ohci_clk),
> + CLKDEV_CON_DEV_ID("hclk", "600000.fb", &hck1),
> CLKDEV_CON_DEV_ID("spi_clk", "fffc8000.spi", &spi0_clk),
> CLKDEV_CON_DEV_ID("spi_clk", "fffcc000.spi", &spi1_clk),
> CLKDEV_CON_DEV_ID("mci_clk", "fffa8000.mmc", &mmc_clk),
>


--
Nicolas Ferre

2014-01-15 08:34:58

by Jean-Jacques Hiblot

[permalink] [raw]
Subject: Re: [PATCH v2 00/12] Device Tree support for the at91sam9261ek

2014/1/14 Nicolas Ferre <[email protected]>:
> On 09/01/2014 13:31, Jean-Jacques Hiblot :
>> This patch set aims at bringing a basic device tree support for the sam9261.
>> It's mostly based on the sam9263 stuff.
>
> Nice! Thanks a lot for stepping up.
>
> One general comment though: I would like to have new AT91 SoC described
> in DT integrating mainline with Common Clock Framework implemented.
> I feel that it would make sense to directly convert sam9261 to CCF to
> avoid the pain of an intermediary DT description that would last only
> for a couple of kernel revisions.
This is a work in progress. I'm doing it on top of this patch serie.
>
>> It introduces a new driver for the smc/ebi bus. It's used to configure the EBI
>> from the DT. I haven't documented its DT bindings yet. Timings can be provided
>> as raw values or nanoseconds.
>
> Thanks a lot for taking this SMC-as-a-driver task: it is very cool to
> have it!
>
> I will try to review your patches in the coming days... But Boris
> already did a good job at this ;-)
>
>> Change since V1:
>> * changed the DT representation to use address translation and separate the
>> timings' configuration from the device properties by adding a "simple-bus"
>> inetrmediate node.
>> * moved the smc driver from drivers/bus to drivers/memmory
>> * smc driver now accepts timings in nanoseconds as well as raw register values
>> * smc driver can clip the timings if they're out of bound and dump them to the
>> console
>> * DM9000 timings are now described in nanosecs (for the virtue of example)
>>
>> supported features:
>> * dbgu
>> * nand
>> * lcd
>> * ethernet
>> * leds
>>
>> Jean-Jacques
>>
>> Jean-Jacques Hiblot (12):
>> at91: dt: Add at91sam9261 dt SoC support
>> at91: dt: sam9261: Basic Device Tree support for the at91sam9261ek
>> at91: dt: sam9261: Added support for the lcd display
>> at91: smc: export sam9_smc_cs_read and sam9_smc_cs_configure.
>> at91: smc: Increased the size of tdf_cycles in struct sam9_smc_config.
>> at91: smc: Adds helper functions to validate and clip the smc timings.
>> at91: dt: smc: Added smc bus driver
>> at91: sam9261: Add a clock definition for the smc
>> at91: dt: sam9261: Pinmux DT entries for the SMC/EBI interface
>> at91: dt: sam9261: Add an entry in the DT for the SMC/EBI bus driver.
>> at91: dt: sam9261: moved the NAND under the smc node
>> at91: dt: sam9261: Added DM9000 in the device tree
>>
>> arch/arm/boot/dts/Makefile | 2 +
>> arch/arm/boot/dts/at91sam9261.dtsi | 639 +++++++++++++++++++++++++
>> arch/arm/boot/dts/at91sam9261ek.dts | 164 +++++++
>> arch/arm/mach-at91/at91sam9261.c | 17 +
>> arch/arm/mach-at91/include/mach/at91sam9_smc.h | 6 +-
>> arch/arm/mach-at91/sam9_smc.c | 81 +++-
>> drivers/memory/Kconfig | 10 +
>> drivers/memory/Makefile | 1 +
>> drivers/memory/atmel-smc.c | 431 +++++++++++++++++
>> 9 files changed, 1348 insertions(+), 3 deletions(-)
>> create mode 100644 arch/arm/boot/dts/at91sam9261.dtsi
>> create mode 100644 arch/arm/boot/dts/at91sam9261ek.dts
>> create mode 100644 drivers/memory/atmel-smc.c
>>
>
>
> --
> Nicolas Ferre

2014-01-15 09:44:41

by Nicolas Ferre

[permalink] [raw]
Subject: Re: [PATCH v2 06/12] at91: smc: Adds helper functions to validate and clip the smc timings.

On 09/01/2014 13:31, Jean-Jacques Hiblot :
> This patchs implememnts 2 functions to help with the configuration of a
> chip-select's timing:
> * sam9_smc_check_cs_configuration : checks that the values would fit in the
> registers.
> * sam9_smc_clip_cs_configuration : clip the values to their maximum.
>
> Signed-off-by: Jean-Jacques Hiblot <[email protected]>
> ---
> arch/arm/mach-at91/include/mach/at91sam9_smc.h | 2 +
> arch/arm/mach-at91/sam9_smc.c | 77 ++++++++++++++++++++++++++
> 2 files changed, 79 insertions(+)
>
> diff --git a/arch/arm/mach-at91/include/mach/at91sam9_smc.h b/arch/arm/mach-at91/include/mach/at91sam9_smc.h
> index c3e29311..615ac56 100644
> --- a/arch/arm/mach-at91/include/mach/at91sam9_smc.h
> +++ b/arch/arm/mach-at91/include/mach/at91sam9_smc.h
> @@ -47,6 +47,8 @@ extern void sam9_smc_read_mode(int id, int cs, struct sam9_smc_config *config);
> extern void sam9_smc_write_mode(int id, int cs, struct sam9_smc_config *config);
> extern void sam9_smc_cs_read(void __iomem *, struct sam9_smc_config *config);
> extern void sam9_smc_cs_configure(void __iomem *, struct sam9_smc_config *cfg);
> +extern int sam9_smc_check_cs_configuration(struct sam9_smc_config *config);
> +extern void sam9_smc_clip_cs_configuration(struct sam9_smc_config *config);
> #endif
>
> #define AT91_SMC_SETUP 0x00 /* Setup Register for CS n */
> diff --git a/arch/arm/mach-at91/sam9_smc.c b/arch/arm/mach-at91/sam9_smc.c
> index d7a6156..fe3c492 100644
> --- a/arch/arm/mach-at91/sam9_smc.c
> +++ b/arch/arm/mach-at91/sam9_smc.c
> @@ -23,6 +23,83 @@
>
> static void __iomem *smc_base_addr[2];
>
> +static int count_trailing_zeroes(u32 x)

Don't we have something generic for this?

Check include/asm-generic/bitops/count_zeros.h

> +{
> + int ret = 0;
> + if (!(x & 0xFFFF)) {
> + ret += 16;
> + x = x >> 16;
> + }
> + if (!(x & 0xFF)) {
> + ret += 8;
> + x = x >> 8;
> + }
> + if (!(x & 0xF)) {
> + ret += 4;
> + x = x >> 4;
> + }
> + if (!(x & 0x3)) {
> + ret += 2;
> + x = x >> 2;
> + }
> + if (!(x & 0x1)) {
> + ret += 1;
> + x = x >> 1;
> + }
> + if (!(x & 0x1))
> + ret += 1;
> +
> + return ret;
> +}
> +
> +
> +#define __CHECK_CFG(config, x, y) do {\
> + if (x##_(config->y) > x) {\
> + pr_debug("error: %s (0x%x) is out of range\n", #y,\
> + config->y >> count_trailing_zeroes(x));\
> + return -EINVAL;\
> + } \
> + } while (0)

I do not like the use of macro for this. You can convert them to
functions and it would increase readability. I am pretty confident that
gcc will optimize it so that is won't impact performance.

> +int sam9_smc_check_cs_configuration(struct sam9_smc_config *config)
> +{
> + __CHECK_CFG(config, AT91_SMC_NWESETUP, nwe_setup);
> + __CHECK_CFG(config, AT91_SMC_NCS_WRSETUP, ncs_write_setup);
> + __CHECK_CFG(config, AT91_SMC_NRDSETUP, nrd_setup);
> + __CHECK_CFG(config, AT91_SMC_NCS_RDSETUP, ncs_read_setup);
> + __CHECK_CFG(config, AT91_SMC_NWEPULSE, nwe_pulse);
> + __CHECK_CFG(config, AT91_SMC_NCS_WRPULSE, ncs_write_pulse);
> + __CHECK_CFG(config, AT91_SMC_NRDPULSE, nrd_pulse);
> + __CHECK_CFG(config, AT91_SMC_NCS_RDPULSE, ncs_read_pulse);
> + __CHECK_CFG(config, AT91_SMC_NWECYCLE, write_cycle);
> + __CHECK_CFG(config, AT91_SMC_NRDCYCLE, read_cycle);
> + __CHECK_CFG(config, AT91_SMC_TDF, tdf_cycles);
> + return 0;
> +}
> +
> +#define __CLIP_CFG(config, x, y) do {\
> + if (x##_(config->y) > x) {\
> + config->y = x >> count_trailing_zeroes(x);\
> + pr_debug("clipping %s to %d\n", #y, config->y);\
> + } \
> + } while (0)

Ditto.

> +
> +void sam9_smc_clip_cs_configuration(struct sam9_smc_config *config)
> +{
> + __CLIP_CFG(config, AT91_SMC_NWESETUP, nwe_setup);
> + __CLIP_CFG(config, AT91_SMC_NCS_WRSETUP, ncs_write_setup);
> + __CLIP_CFG(config, AT91_SMC_NRDSETUP, nrd_setup);
> + __CLIP_CFG(config, AT91_SMC_NCS_RDSETUP, ncs_read_setup);
> + __CLIP_CFG(config, AT91_SMC_NWEPULSE, nwe_pulse);
> + __CLIP_CFG(config, AT91_SMC_NCS_WRPULSE, ncs_write_pulse);
> + __CLIP_CFG(config, AT91_SMC_NRDPULSE, nrd_pulse);
> + __CLIP_CFG(config, AT91_SMC_NCS_RDPULSE, ncs_read_pulse);
> + __CLIP_CFG(config, AT91_SMC_NWECYCLE, write_cycle);
> + __CLIP_CFG(config, AT91_SMC_NRDCYCLE, read_cycle);
> + __CLIP_CFG(config, AT91_SMC_TDF, tdf_cycles);
> +
> +}
> +
> static void sam9_smc_cs_write_mode(void __iomem *base,
> struct sam9_smc_config *config)
> {
>


--
Nicolas Ferre

2014-01-15 10:00:26

by Jean-Jacques Hiblot

[permalink] [raw]
Subject: Re: [PATCH v2 06/12] at91: smc: Adds helper functions to validate and clip the smc timings.

2014/1/15 Nicolas Ferre <[email protected]>:
> On 09/01/2014 13:31, Jean-Jacques Hiblot :
>> This patchs implememnts 2 functions to help with the configuration of a
>> chip-select's timing:
>> * sam9_smc_check_cs_configuration : checks that the values would fit in the
>> registers.
>> * sam9_smc_clip_cs_configuration : clip the values to their maximum.
>>
>> Signed-off-by: Jean-Jacques Hiblot <[email protected]>
>> ---
>> arch/arm/mach-at91/include/mach/at91sam9_smc.h | 2 +
>> arch/arm/mach-at91/sam9_smc.c | 77 ++++++++++++++++++++++++++
>> 2 files changed, 79 insertions(+)
>>
>> diff --git a/arch/arm/mach-at91/include/mach/at91sam9_smc.h b/arch/arm/mach-at91/include/mach/at91sam9_smc.h
>> index c3e29311..615ac56 100644
>> --- a/arch/arm/mach-at91/include/mach/at91sam9_smc.h
>> +++ b/arch/arm/mach-at91/include/mach/at91sam9_smc.h
>> @@ -47,6 +47,8 @@ extern void sam9_smc_read_mode(int id, int cs, struct sam9_smc_config *config);
>> extern void sam9_smc_write_mode(int id, int cs, struct sam9_smc_config *config);
>> extern void sam9_smc_cs_read(void __iomem *, struct sam9_smc_config *config);
>> extern void sam9_smc_cs_configure(void __iomem *, struct sam9_smc_config *cfg);
>> +extern int sam9_smc_check_cs_configuration(struct sam9_smc_config *config);
>> +extern void sam9_smc_clip_cs_configuration(struct sam9_smc_config *config);
>> #endif
>>
>> #define AT91_SMC_SETUP 0x00 /* Setup Register for CS n */
>> diff --git a/arch/arm/mach-at91/sam9_smc.c b/arch/arm/mach-at91/sam9_smc.c
>> index d7a6156..fe3c492 100644
>> --- a/arch/arm/mach-at91/sam9_smc.c
>> +++ b/arch/arm/mach-at91/sam9_smc.c
>> @@ -23,6 +23,83 @@
>>
>> static void __iomem *smc_base_addr[2];
>>
>> +static int count_trailing_zeroes(u32 x)
>
> Don't we have something generic for this?
>
> Check include/asm-generic/bitops/count_zeros.h

I wonder how I could have missed this one :o)

>
>> +{
>> + int ret = 0;
>> + if (!(x & 0xFFFF)) {
>> + ret += 16;
>> + x = x >> 16;
>> + }
>> + if (!(x & 0xFF)) {
>> + ret += 8;
>> + x = x >> 8;
>> + }
>> + if (!(x & 0xF)) {
>> + ret += 4;
>> + x = x >> 4;
>> + }
>> + if (!(x & 0x3)) {
>> + ret += 2;
>> + x = x >> 2;
>> + }
>> + if (!(x & 0x1)) {
>> + ret += 1;
>> + x = x >> 1;
>> + }
>> + if (!(x & 0x1))
>> + ret += 1;
>> +
>> + return ret;
>> +}
>> +
>> +
>> +#define __CHECK_CFG(config, x, y) do {\
>> + if (x##_(config->y) > x) {\
>> + pr_debug("error: %s (0x%x) is out of range\n", #y,\
>> + config->y >> count_trailing_zeroes(x));\
>> + return -EINVAL;\
>> + } \
>> + } while (0)
>
> I do not like the use of macro for this. You can convert them to
> functions and it would increase readability. I am pretty confident that
> gcc will optimize it so that is won't impact performance.

It's not a matter of performance. I wanted to use the stringification
for the debug message.

>
>> +int sam9_smc_check_cs_configuration(struct sam9_smc_config *config)
>> +{
>> + __CHECK_CFG(config, AT91_SMC_NWESETUP, nwe_setup);
>> + __CHECK_CFG(config, AT91_SMC_NCS_WRSETUP, ncs_write_setup);
>> + __CHECK_CFG(config, AT91_SMC_NRDSETUP, nrd_setup);
>> + __CHECK_CFG(config, AT91_SMC_NCS_RDSETUP, ncs_read_setup);
>> + __CHECK_CFG(config, AT91_SMC_NWEPULSE, nwe_pulse);
>> + __CHECK_CFG(config, AT91_SMC_NCS_WRPULSE, ncs_write_pulse);
>> + __CHECK_CFG(config, AT91_SMC_NRDPULSE, nrd_pulse);
>> + __CHECK_CFG(config, AT91_SMC_NCS_RDPULSE, ncs_read_pulse);
>> + __CHECK_CFG(config, AT91_SMC_NWECYCLE, write_cycle);
>> + __CHECK_CFG(config, AT91_SMC_NRDCYCLE, read_cycle);
>> + __CHECK_CFG(config, AT91_SMC_TDF, tdf_cycles);
>> + return 0;
>> +}
>> +
>> +#define __CLIP_CFG(config, x, y) do {\
>> + if (x##_(config->y) > x) {\
>> + config->y = x >> count_trailing_zeroes(x);\
>> + pr_debug("clipping %s to %d\n", #y, config->y);\
>> + } \
>> + } while (0)
>
> Ditto.
>
>> +
>> +void sam9_smc_clip_cs_configuration(struct sam9_smc_config *config)
>> +{
>> + __CLIP_CFG(config, AT91_SMC_NWESETUP, nwe_setup);
>> + __CLIP_CFG(config, AT91_SMC_NCS_WRSETUP, ncs_write_setup);
>> + __CLIP_CFG(config, AT91_SMC_NRDSETUP, nrd_setup);
>> + __CLIP_CFG(config, AT91_SMC_NCS_RDSETUP, ncs_read_setup);
>> + __CLIP_CFG(config, AT91_SMC_NWEPULSE, nwe_pulse);
>> + __CLIP_CFG(config, AT91_SMC_NCS_WRPULSE, ncs_write_pulse);
>> + __CLIP_CFG(config, AT91_SMC_NRDPULSE, nrd_pulse);
>> + __CLIP_CFG(config, AT91_SMC_NCS_RDPULSE, ncs_read_pulse);
>> + __CLIP_CFG(config, AT91_SMC_NWECYCLE, write_cycle);
>> + __CLIP_CFG(config, AT91_SMC_NRDCYCLE, read_cycle);
>> + __CLIP_CFG(config, AT91_SMC_TDF, tdf_cycles);
>> +
>> +}
>> +
>> static void sam9_smc_cs_write_mode(void __iomem *base,
>> struct sam9_smc_config *config)
>> {
>>
>
>
> --
> Nicolas Ferre

2014-01-15 10:13:45

by Jean-Jacques Hiblot

[permalink] [raw]
Subject: Re: [PATCH v2 01/12] at91: dt: Add at91sam9261 dt SoC support

2014/1/14 Nicolas Ferre <[email protected]>:
> On 09/01/2014 13:31, Jean-Jacques Hiblot :
>> This patch adds the basics to support the Device Tree on a sam9261-based platform
>>
>> Signed-off-by: Jean-Jacques Hiblot <[email protected]>
>> ---
>> arch/arm/boot/dts/at91sam9261.dtsi | 476 +++++++++++++++++++++++++++++++++++++
>> arch/arm/mach-at91/at91sam9261.c | 15 ++
>> 2 files changed, 491 insertions(+)
>> create mode 100644 arch/arm/boot/dts/at91sam9261.dtsi
>>
>> diff --git a/arch/arm/boot/dts/at91sam9261.dtsi b/arch/arm/boot/dts/at91sam9261.dtsi
>> new file mode 100644
>> index 0000000..773c3d6
>> --- /dev/null
>> +++ b/arch/arm/boot/dts/at91sam9261.dtsi
>> @@ -0,0 +1,476 @@
>> +/*
>> + * at91sam9261.dtsi - Device Tree Include file for AT91SAM9261 SoC
>> + *
>> + * Copyright (C) 2013 Jean-Jacques Hiblot <[email protected]>
>> + *
>> + * Licensed under GPLv2 only.
>> + */
>> +
>> +#include "skeleton.dtsi"
>> +#include <dt-bindings/pinctrl/at91.h>
>> +#include <dt-bindings/interrupt-controller/irq.h>
>> +#include <dt-bindings/gpio/gpio.h>
>> +
>> +/ {
>> + model = "Atmel AT91SAM9261 family SoC";
>> + compatible = "atmel,at91sam9261";
>> + interrupt-parent = <&aic>;
>> +
>> + aliases {
>> + serial0 = &dbgu;
>> + serial1 = &usart0;
>> + serial2 = &usart1;
>> + serial3 = &usart2;
>> + gpio0 = &pioA;
>> + gpio1 = &pioB;
>> + gpio2 = &pioC;
>> + tcb0 = &tcb0;
>> + i2c0 = &i2c0;
>> + ssc0 = &ssc0;
>> + ssc1 = &ssc1;
>> + };
>> + cpus {
>> + #address-cells = <0>;
>> + #size-cells = <0>;
>> +
>> + cpu {
>> + compatible = "arm,arm926ej-s";
>> + device_type = "cpu";
>> + };
>> + };
>> +
>> + memory {
>> + reg = <0x20000000 0x08000000>;
>> + };
>> +
>> + ahb {
>> + compatible = "simple-bus";
>> + #address-cells = <1>;
>> + #size-cells = <1>;
>> + ranges;
>> +
>> + apb {
>> + compatible = "simple-bus";
>> + #address-cells = <1>;
>> + #size-cells = <1>;
>> + ranges;
>
> I know that it is not always done but can you please sort all nodes by
> ascending address order? It is always simple to deal with node additions
> when sorted this way.
just to clarify, ascending order means ascending address order, right ?

>
>> +
>> + aic: interrupt-controller@fffff000 {
>> + #interrupt-cells = <3>;
>> + compatible = "atmel,at91rm9200-aic";
>> + interrupt-controller;
>> + reg = <0xfffff000 0x200>;
>> + atmel,external-irqs = <29 30 31>;
>> + };
>> +
>> + pmc: pmc@fffffc00 {
>> + compatible = "atmel,at91rm9200-pmc";
>> + reg = <0xfffffc00 0x100>;
>> + };
>> +
>> + ramc: ramc@ffffea00 {
>> + compatible = "atmel,at91sam9260-sdramc";
>> + reg = <0xffffea00 0x200>;
>> + };
>> +
>> + pit: timer@fffffd30 {
>> + compatible = "atmel,at91sam9260-pit";
>> + reg = <0xfffffd30 0xf>;
>> + interrupts = <1 IRQ_TYPE_LEVEL_HIGH 7>;
>> + };
>> +
>> + tcb0: timer@fffa0000 {
>> + compatible = "atmel,at91rm9200-tcb";
>> + reg = <0xfffa0000 0x100>;
>> + interrupts = < 17 IRQ_TYPE_LEVEL_HIGH 0
>> + 18 IRQ_TYPE_LEVEL_HIGH 0
>> + 19 IRQ_TYPE_LEVEL_HIGH 0
>> + >;
>> + status = "disabled";
>> + };
>> +
>> + rstc@fffffd00 {
>> + compatible = "atmel,at91sam9260-rstc";
>> + reg = <0xfffffd00 0x10>;
>> + };
>> +
>> + shdwc@fffffd10 {
>> + compatible = "atmel,at91sam9260-shdwc";
>> + reg = <0xfffffd10 0x10>;
>> + };
>> +
>> + pinctrl@fffff400 {
>> + #address-cells = <1>;
>> + #size-cells = <1>;
>> + compatible = "atmel,at91rm9200-pinctrl", "simple-bus";
>> + ranges = <0xfffff400 0xfffff400 0xa00>;
>> +
>> + atmel,mux-mask = <
>> + /* A B */
>> + 0xffffffff 0xfffffff7 /* pioA */
>> + 0xffffffff 0xfffffff4 /* pioB */
>> + 0xffffffff 0xffffff07 /* pioC */
>> + >;
>> +
>> + /* shared pinctrl settings */
>> + dbgu {
>> + pinctrl_dbgu: dbgu-0 {
>> + atmel,pins =
>> + <AT91_PIOA 9 AT91_PERIPH_A AT91_PINCTRL_NONE /* PA9 periph A */
>
> You may have to remove comments for pin descriptions. It is not needed
> anymore with pre-processor macros.
That is a relief

>
>> + AT91_PIOA 10 AT91_PERIPH_A AT91_PINCTRL_PULL_UP>; /* PA10 periph A with pullup */
>> + };
>> + };
>> +
>> + usart0 {
>> + pinctrl_usart0: usart0-0 {
>> + atmel,pins =
>> + <AT91_PIOC 8 AT91_PERIPH_A AT91_PINCTRL_PULL_UP /* PC8 periph A with pullup */
>> + AT91_PIOC 9 AT91_PERIPH_A AT91_PINCTRL_NONE>; /* PC9 periph A */
>> + };
>> +
>> + pinctrl_usart0_rts: usart0_rts-0 {
>> + atmel,pins =
>> + <AT91_PIOC 10 AT91_PERIPH_A AT91_PINCTRL_NONE>; /* PC10 periph A */
>> + };
>> +
>> + pinctrl_usart0_cts: usart0_cts-0 {
>> + atmel,pins =
>> + <AT91_PIOC 11 AT91_PERIPH_A AT91_PINCTRL_NONE>; /* PC11 periph A */
>> + };
>> + };
>> +
>> + usart1 {
>> + pinctrl_usart1: usart1-0 {
>> + atmel,pins =
>> + <AT91_PIOC 12 AT91_PERIPH_A AT91_PINCTRL_PULL_UP /* PC12 periph A with pullup */
>> + AT91_PIOC 13 AT91_PERIPH_A AT91_PINCTRL_NONE>; /* PC13 periph A */
>> + };
>> +
>> + pinctrl_usart1_rts: usart1_rts-0 {
>> + atmel,pins =
>> + <AT91_PIOA 12 AT91_PERIPH_B AT91_PINCTRL_NONE>; /* PA12 periph B */
>> + };
>> +
>> + pinctrl_usart1_cts: usart1_cts-0 {
>> + atmel,pins =
>> + <AT91_PIOA 13 AT91_PERIPH_B AT91_PINCTRL_NONE>; /* PA13 periph B */
>> + };
>> + };
>> +
>> + usart2 {
>> + pinctrl_usart2: usart2-0 {
>> + atmel,pins =
>> + <AT91_PIOC 14 AT91_PERIPH_A AT91_PINCTRL_PULL_UP /* PC14 periph A with pullup */
>> + AT91_PIOC 15 AT91_PERIPH_A AT91_PINCTRL_NONE>; /* PC15 periph A */
>> + };
>> +
>> + pinctrl_usart2_rts: usart2_rts-0 {
>> + atmel,pins =
>> + <AT91_PIOA 15 AT91_PERIPH_B AT91_PINCTRL_NONE>; /* PA15 periph B */
>> + };
>> +
>> + pinctrl_usart2_cts: usart2_cts-0 {
>> + atmel,pins =
>> + <AT91_PIOA 16 AT91_PERIPH_B AT91_PINCTRL_NONE>; /* PA16 periph B */
>> + };
>> + };
>> +
>> + nand {
>> + pinctrl_nand: nand-0 {
>> + atmel,pins =
>> + <AT91_PIOC 15 AT91_PERIPH_GPIO AT91_PINCTRL_PULL_UP /* PC15 gpio RDY pin pull_up*/
>> + AT91_PIOC 14 AT91_PERIPH_GPIO AT91_PINCTRL_PULL_UP>; /* PC14 gpio enable pin pull_up */
>> + };
>> + };
>> +
>> + mmc0 {
>> + pinctrl_mmc0_clk: mmc0_clk-0 {
>> + atmel,pins =
>> + <AT91_PIOA 2 AT91_PERIPH_B AT91_PINCTRL_NONE>; /* PA2 periph B */
>> + };
>> +
>> + pinctrl_mmc0_slot0_cmd_dat0: mmc0_slot0_cmd_dat0-0 {
>> + atmel,pins =
>> + <AT91_PIOA 1 AT91_PERIPH_B AT91_PINCTRL_PULL_UP /* PA1 periph B with pullup */
>> + AT91_PIOA 0 AT91_PERIPH_B AT91_PINCTRL_PULL_UP>; /* PA0 periph B with pullup */
>> + };
>> +
>> + pinctrl_mmc0_slot0_dat1_3: mmc0_slot0_dat1_3-0 {
>> + atmel,pins =
>> + <AT91_PIOA 4 AT91_PERIPH_B AT91_PINCTRL_PULL_UP /* PA4 periph B with pullup */
>> + AT91_PIOA 5 AT91_PERIPH_B AT91_PINCTRL_PULL_UP /* PA5 periph B with pullup */
>> + AT91_PIOA 6 AT91_PERIPH_B AT91_PINCTRL_PULL_UP>; /* PA6 periph B with pullup */
>> + };
>> + };
>> +
>> + ssc0 {
>> + pinctrl_ssc0_tx: ssc0_tx-0 {
>> + atmel,pins =
>> + <AT91_PIOB 21 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB21 periph A */
>> + AT91_PIOB 22 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB22 periph A */
>> + AT91_PIOB 23 AT91_PERIPH_A AT91_PINCTRL_NONE>; /* PB23 periph A */
>> + };
>> +
>> + pinctrl_ssc0_rx: ssc0_rx-0 {
>> + atmel,pins =
>> + <AT91_PIOB 24 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB24 periph A */
>> + AT91_PIOB 25 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB25 periph A */
>> + AT91_PIOB 26 AT91_PERIPH_A AT91_PINCTRL_NONE>; /* PB26 periph A */
>> + };
>> + };
>> +
>> + ssc1 {
>> + pinctrl_ssc1_tx: ssc1_tx-0 {
>> + atmel,pins =
>> + <AT91_PIOA 17 AT91_PERIPH_B AT91_PINCTRL_NONE /* PA17 periph B */
>> + AT91_PIOA 18 AT91_PERIPH_B AT91_PINCTRL_NONE /* PA18 periph B */
>> + AT91_PIOA 19 AT91_PERIPH_B AT91_PINCTRL_NONE>; /* PA19 periph B */
>> + };
>> +
>> + pinctrl_ssc1_rx: ssc1_rx-0 {
>> + atmel,pins =
>> + <AT91_PIOA 20 AT91_PERIPH_B AT91_PINCTRL_NONE /* PA20 periph B */
>> + AT91_PIOA 21 AT91_PERIPH_B AT91_PINCTRL_NONE /* PA21 periph B */
>> + AT91_PIOA 22 AT91_PERIPH_B AT91_PINCTRL_NONE>; /* PA22 periph B */
>> + };
>> + };
>> +
>> + spi0 {
>> + pinctrl_spi0: spi0-0 {
>> + atmel,pins =
>> + <AT91_PIOA 0 AT91_PERIPH_A AT91_PINCTRL_NONE /* PA0 periph A SPI0_MISO pin */
>> + AT91_PIOA 1 AT91_PERIPH_A AT91_PINCTRL_NONE /* PA1 periph A SPI0_MOSI pin */
>> + AT91_PIOA 2 AT91_PERIPH_A AT91_PINCTRL_NONE>; /* PA2 periph A SPI0_SPCK pin */
>> + };
>> + };
>> +
>> + spi1 {
>> + pinctrl_spi1: spi1-0 {
>> + atmel,pins =
>> + <AT91_PIOB 30 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB30 periph A SPI1_MISO pin */
>> + AT91_PIOB 31 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB31 periph A SPI1_MOSI pin */
>> + AT91_PIOB 29 AT91_PERIPH_A AT91_PINCTRL_NONE>; /* PB29 periph A SPI1_SPCK pin */
>> + };
>> + };
>> +
>> + tcb0 {
>> + pinctrl_tcb0_tclk0: tcb0_tclk0-0 {
>> + atmel,pins = <AT91_PIOC 16 AT91_PERIPH_B AT91_PINCTRL_NONE>;
>> + };
>> +
>> + pinctrl_tcb0_tclk1: tcb0_tclk1-0 {
>> + atmel,pins = <AT91_PIOC 17 AT91_PERIPH_B AT91_PINCTRL_NONE>;
>> + };
>> +
>> + pinctrl_tcb0_tclk2: tcb0_tclk2-0 {
>> + atmel,pins = <AT91_PIOC 18 AT91_PERIPH_B AT91_PINCTRL_NONE>;
>> + };
>> +
>> + pinctrl_tcb0_tioa0: tcb0_tioa0-0 {
>> + atmel,pins = <AT91_PIOC 19 AT91_PERIPH_B AT91_PINCTRL_NONE>;
>> + };
>> +
>> + pinctrl_tcb0_tioa1: tcb0_tioa1-0 {
>> + atmel,pins = <AT91_PIOC 21 AT91_PERIPH_B AT91_PINCTRL_NONE>;
>> + };
>> +
>> + pinctrl_tcb0_tioa2: tcb0_tioa2-0 {
>> + atmel,pins = <AT91_PIOC 23 AT91_PERIPH_B AT91_PINCTRL_NONE>;
>> + };
>> +
>> + pinctrl_tcb0_tiob0: tcb0_tiob0-0 {
>> + atmel,pins = <AT91_PIOC 20 AT91_PERIPH_B AT91_PINCTRL_NONE>;
>> + };
>> +
>> + pinctrl_tcb0_tiob1: tcb0_tiob1-0 {
>> + atmel,pins = <AT91_PIOC 22 AT91_PERIPH_B AT91_PINCTRL_NONE>;
>> + };
>> +
>> + pinctrl_tcb0_tiob2: tcb0_tiob2-0 {
>> + atmel,pins = <AT91_PIOC 24 AT91_PERIPH_B AT91_PINCTRL_NONE>;
>> + };
>> + };
>> +
>> + pioA: gpio@fffff400 {
>> + compatible = "atmel,at91rm9200-gpio";
>> + reg = <0xfffff400 0x200>;
>> + interrupts = <2 IRQ_TYPE_LEVEL_HIGH 1>;
>> + #gpio-cells = <2>;
>> + gpio-controller;
>> + interrupt-controller;
>> + #interrupt-cells = <2>;
>> + };
>> +
>> + pioB: gpio@fffff600 {
>> + compatible = "atmel,at91rm9200-gpio";
>> + reg = <0xfffff600 0x200>;
>> + interrupts = <3 IRQ_TYPE_LEVEL_HIGH 1>;
>> + #gpio-cells = <2>;
>> + gpio-controller;
>> + interrupt-controller;
>> + #interrupt-cells = <2>;
>> + };
>> +
>> + pioC: gpio@fffff800 {
>> + compatible = "atmel,at91rm9200-gpio";
>> + reg = <0xfffff800 0x200>;
>> + interrupts = <4 IRQ_TYPE_LEVEL_HIGH 1>;
>> + #gpio-cells = <2>;
>> + gpio-controller;
>> + interrupt-controller;
>> + #interrupt-cells = <2>;
>> + };
>> + };
>> +
>> + dbgu: serial@fffff200 {
>> + compatible = "atmel,at91sam9260-usart";
>> + reg = <0xfffff200 0x200>;
>> + interrupts = <1 IRQ_TYPE_LEVEL_HIGH 7>;
>> + pinctrl-names = "default";
>> + pinctrl-0 = <&pinctrl_dbgu>;
>> + status = "disabled";
>> + };
>> +
>> + usart0: serial@fffb0000 {
>> + compatible = "atmel,at91sam9260-usart";
>> + reg = <0xfffb0000 0x200>;
>> + interrupts = <6 IRQ_TYPE_LEVEL_HIGH 5>;
>> + atmel,use-dma-rx;
>> + atmel,use-dma-tx;
>> + pinctrl-names = "default";
>> + pinctrl-0 = <&pinctrl_usart0>;
>> + status = "disabled";
>> + };
>> +
>> + usart1: serial@ffffb400 {
>> + compatible = "atmel,at91sam9260-usart";
>> + reg = <0xfffb4000 0x200>;
>> + interrupts = <7 IRQ_TYPE_LEVEL_HIGH 5>;
>> + atmel,use-dma-rx;
>> + atmel,use-dma-tx;
>> + pinctrl-names = "default";
>> + pinctrl-0 = <&pinctrl_usart1>;
>> + status = "disabled";
>> + };
>> +
>> + usart2: serial@fff94000 {
>> + compatible = "atmel,at91sam9260-usart";
>> + reg = <0xfffb8000 0x200>;
>> + interrupts = <8 IRQ_TYPE_LEVEL_HIGH 5>;
>> + atmel,use-dma-rx;
>> + atmel,use-dma-tx;
>> + pinctrl-names = "default";
>> + pinctrl-0 = <&pinctrl_usart2>;
>> + status = "disabled";
>> + };
>> +
>> + ssc0: ssc@fffbc000 {
>> + compatible = "atmel,at91rm9200-ssc";
>> + reg = <0xfffbc000 0x4000>;
>> + interrupts = <14 IRQ_TYPE_LEVEL_HIGH 5>;
>> + pinctrl-names = "default";
>> + pinctrl-0 = <&pinctrl_ssc0_tx &pinctrl_ssc0_rx>;
>> + status = "disabled";
>> + };
>> +
>> + ssc1: ssc@fffc0000 {
>> + compatible = "atmel,at91rm9200-ssc";
>> + reg = <0xfffc0000 0x4000>;
>> + interrupts = <15 IRQ_TYPE_LEVEL_HIGH 5>;
>> + pinctrl-names = "default";
>> + pinctrl-0 = <&pinctrl_ssc1_tx &pinctrl_ssc1_rx>;
>> + status = "disabled";
>> + };
>> +
>> + usb1: gadget@fffa4000 {
>> + compatible = "atmel,at91rm9200-udc";
>> + reg = <0xfffa4000 0x4000>;
>> + interrupts = <10 IRQ_TYPE_LEVEL_HIGH 2>;
>> + status = "disabled";
>> + };
>> +
>> + i2c0: i2c@fffac000 {
>> + compatible = "atmel,at91sam9261-i2c";
>
> isn't it "atmel,at91sam9260-i2c" ?
I don't know. the i2c driver whows a difference between the 9260 and
the 9261 regarding the clk_max_div. But I forgot to add the 9261 in
the dt ids table of the twi driver.
>
>> + reg = <0xfffac000 0x100>;
>> + interrupts = <11 IRQ_TYPE_LEVEL_HIGH 6>;
>> + #address-cells = <1>;
>> + #size-cells = <0>;
>> + status = "disabled";
>> + };
>> +
>> + mmc0: mmc@fffa8000 {
>> + compatible = "atmel,hsmci";
>> + reg = <0xfffa8000 0x600>;
>> + interrupts = <9 IRQ_TYPE_LEVEL_HIGH 0>;
>> + #address-cells = <1>;
>> + #size-cells = <0>;
>> + status = "disabled";
>> + };
>> +
>> + watchdog@fffffd40 {
>> + compatible = "atmel,at91sam9260-wdt";
>> + reg = <0xfffffd40 0x10>;
>> + status = "disabled";
>> + };
>> +
>> + spi0: spi@fffc8000 {
>> + #address-cells = <1>;
>> + #size-cells = <0>;
>> + compatible = "atmel,at91rm9200-spi";
>> + reg = <0xfffc8000 0x200>;
>> + interrupts = <12 IRQ_TYPE_LEVEL_HIGH 3>;
>> + pinctrl-names = "default";
>> + pinctrl-0 = <&pinctrl_spi0>;
>> + status = "disabled";
>> + };
>> +
>> + spi1: spi@fffcc000 {
>> + #address-cells = <1>;
>> + #size-cells = <0>;
>> + compatible = "atmel,at91rm9200-spi";
>> + reg = <0xfffcc000 0x200>;
>> + interrupts = <13 IRQ_TYPE_LEVEL_HIGH 3>;
>> + pinctrl-names = "default";
>> + pinctrl-0 = <&pinctrl_spi1>;
>> + status = "disabled";
>> + };
>> + };
>> +
>> + nand0: nand@40000000 {
>> + compatible = "atmel,at91rm9200-nand";
>> + #address-cells = <1>;
>> + #size-cells = <1>;
>> + reg = <0x40000000 0x10000000>;
>> + atmel,nand-addr-offset = <22>;
>> + atmel,nand-cmd-offset = <21>;
>> + pinctrl-names = "default";
>> + pinctrl-0 = <&pinctrl_nand>;
>> +
>> + gpios = <&pioC 15 GPIO_ACTIVE_HIGH
>> + &pioC 14 GPIO_ACTIVE_HIGH
>> + 0
>> + >;
>> + status = "disabled";
>> + };
>> +
>> + usb0: ohci@00500000 {
>> + compatible = "atmel,at91rm9200-ohci", "usb-ohci";
>> + reg = <0x00500000 0x100000>;
>> + interrupts = <20 IRQ_TYPE_LEVEL_HIGH 2>;
>> + status = "disabled";
>> + };
>> + };
>> +
>> + i2c@0 {
>> + compatible = "i2c-gpio";
>> + gpios = <&pioA 7 GPIO_ACTIVE_HIGH /* sda */
>> + &pioA 8 GPIO_ACTIVE_HIGH /* scl */
>> + >;
>> + i2c-gpio,sda-open-drain;
>> + i2c-gpio,scl-open-drain;
>> + i2c-gpio,delay-us = <2>; /* ~100 kHz */
>> + #address-cells = <1>;
>> + #size-cells = <0>;
>> + status = "disabled";
>> + };
>> +};
>> diff --git a/arch/arm/mach-at91/at91sam9261.c b/arch/arm/mach-at91/at91sam9261.c
>> index 6276b4c..200d17a 100644
>> --- a/arch/arm/mach-at91/at91sam9261.c
>> +++ b/arch/arm/mach-at91/at91sam9261.c
>> @@ -189,6 +189,21 @@ static struct clk_lookup periph_clocks_lookups[] = {
>> CLKDEV_CON_ID("pioA", &pioA_clk),
>> CLKDEV_CON_ID("pioB", &pioB_clk),
>> CLKDEV_CON_ID("pioC", &pioC_clk),
>> + /* more usart lookup table for DT entries */
>> + CLKDEV_CON_DEV_ID("usart", "fffff200.serial", &mck),
>> + CLKDEV_CON_DEV_ID("usart", "fffb0000.serial", &usart0_clk),
>> + CLKDEV_CON_DEV_ID("usart", "ffffb400.serial", &usart1_clk),
>> + CLKDEV_CON_DEV_ID("usart", "fff94000.serial", &usart2_clk),
>> + /* more tc lookup table for DT entries */
>> + CLKDEV_CON_DEV_ID("t0_clk", "fffa0000.timer", &tc0_clk),
>> + CLKDEV_CON_DEV_ID("hclk", "500000.ohci", &ohci_clk),
>> + CLKDEV_CON_DEV_ID("spi_clk", "fffc8000.spi", &spi0_clk),
>> + CLKDEV_CON_DEV_ID("spi_clk", "fffcc000.spi", &spi1_clk),
>> + CLKDEV_CON_DEV_ID("mci_clk", "fffa8000.mmc", &mmc_clk),
>> + CLKDEV_CON_DEV_ID(NULL, "fffac000.i2c", &twi_clk),
>> + CLKDEV_CON_DEV_ID(NULL, "fffff400.gpio", &pioA_clk),
>> + CLKDEV_CON_DEV_ID(NULL, "fffff600.gpio", &pioB_clk),
>> + CLKDEV_CON_DEV_ID(NULL, "fffff800.gpio", &pioC_clk),
>
> Yes, this is where I would like the CCF to be implemented...
Is it okay it CCF support comes latter or must it be part of this serie?

>
>
>> };
>>
>> static struct clk_lookup usart_clocks_lookups[] = {
>>
>
>
> --
> Nicolas Ferre

2014-01-15 10:15:14

by Nicolas Ferre

[permalink] [raw]
Subject: Re: [PATCH v2 01/12] at91: dt: Add at91sam9261 dt SoC support

On 15/01/2014 11:08, Jean-Jacques Hiblot :
> 2014/1/14 Nicolas Ferre <[email protected]>:
>> On 09/01/2014 13:31, Jean-Jacques Hiblot :
>>> This patch adds the basics to support the Device Tree on a sam9261-based platform
>>>
>>> Signed-off-by: Jean-Jacques Hiblot <[email protected]>
>>> ---
>>> arch/arm/boot/dts/at91sam9261.dtsi | 476 +++++++++++++++++++++++++++++++++++++
>>> arch/arm/mach-at91/at91sam9261.c | 15 ++
>>> 2 files changed, 491 insertions(+)
>>> create mode 100644 arch/arm/boot/dts/at91sam9261.dtsi
>>>
>>> diff --git a/arch/arm/boot/dts/at91sam9261.dtsi b/arch/arm/boot/dts/at91sam9261.dtsi
>>> new file mode 100644
>>> index 0000000..773c3d6
>>> --- /dev/null
>>> +++ b/arch/arm/boot/dts/at91sam9261.dtsi
>>> @@ -0,0 +1,476 @@
>>> +/*
>>> + * at91sam9261.dtsi - Device Tree Include file for AT91SAM9261 SoC
>>> + *
>>> + * Copyright (C) 2013 Jean-Jacques Hiblot <[email protected]>
>>> + *
>>> + * Licensed under GPLv2 only.
>>> + */
>>> +
>>> +#include "skeleton.dtsi"
>>> +#include <dt-bindings/pinctrl/at91.h>
>>> +#include <dt-bindings/interrupt-controller/irq.h>
>>> +#include <dt-bindings/gpio/gpio.h>
>>> +
>>> +/ {
>>> + model = "Atmel AT91SAM9261 family SoC";
>>> + compatible = "atmel,at91sam9261";
>>> + interrupt-parent = <&aic>;
>>> +
>>> + aliases {
>>> + serial0 = &dbgu;
>>> + serial1 = &usart0;
>>> + serial2 = &usart1;
>>> + serial3 = &usart2;
>>> + gpio0 = &pioA;
>>> + gpio1 = &pioB;
>>> + gpio2 = &pioC;
>>> + tcb0 = &tcb0;
>>> + i2c0 = &i2c0;
>>> + ssc0 = &ssc0;
>>> + ssc1 = &ssc1;
>>> + };
>>> + cpus {
>>> + #address-cells = <0>;
>>> + #size-cells = <0>;
>>> +
>>> + cpu {
>>> + compatible = "arm,arm926ej-s";
>>> + device_type = "cpu";
>>> + };
>>> + };
>>> +
>>> + memory {
>>> + reg = <0x20000000 0x08000000>;
>>> + };
>>> +
>>> + ahb {
>>> + compatible = "simple-bus";
>>> + #address-cells = <1>;
>>> + #size-cells = <1>;
>>> + ranges;
>>> +
>>> + apb {
>>> + compatible = "simple-bus";
>>> + #address-cells = <1>;
>>> + #size-cells = <1>;
>>> + ranges;
>>
>> I know that it is not always done but can you please sort all nodes by
>> ascending address order? It is always simple to deal with node additions
>> when sorted this way.
> just to clarify, ascending order means ascending address order, right ?
>
>>
>>> +
>>> + aic: interrupt-controller@fffff000 {
>>> + #interrupt-cells = <3>;
>>> + compatible = "atmel,at91rm9200-aic";
>>> + interrupt-controller;
>>> + reg = <0xfffff000 0x200>;
>>> + atmel,external-irqs = <29 30 31>;
>>> + };
>>> +
>>> + pmc: pmc@fffffc00 {
>>> + compatible = "atmel,at91rm9200-pmc";
>>> + reg = <0xfffffc00 0x100>;
>>> + };
>>> +
>>> + ramc: ramc@ffffea00 {
>>> + compatible = "atmel,at91sam9260-sdramc";
>>> + reg = <0xffffea00 0x200>;
>>> + };
>>> +
>>> + pit: timer@fffffd30 {
>>> + compatible = "atmel,at91sam9260-pit";
>>> + reg = <0xfffffd30 0xf>;
>>> + interrupts = <1 IRQ_TYPE_LEVEL_HIGH 7>;
>>> + };
>>> +
>>> + tcb0: timer@fffa0000 {
>>> + compatible = "atmel,at91rm9200-tcb";
>>> + reg = <0xfffa0000 0x100>;
>>> + interrupts = < 17 IRQ_TYPE_LEVEL_HIGH 0
>>> + 18 IRQ_TYPE_LEVEL_HIGH 0
>>> + 19 IRQ_TYPE_LEVEL_HIGH 0
>>> + >;
>>> + status = "disabled";
>>> + };
>>> +
>>> + rstc@fffffd00 {
>>> + compatible = "atmel,at91sam9260-rstc";
>>> + reg = <0xfffffd00 0x10>;
>>> + };
>>> +
>>> + shdwc@fffffd10 {
>>> + compatible = "atmel,at91sam9260-shdwc";
>>> + reg = <0xfffffd10 0x10>;
>>> + };
>>> +
>>> + pinctrl@fffff400 {
>>> + #address-cells = <1>;
>>> + #size-cells = <1>;
>>> + compatible = "atmel,at91rm9200-pinctrl", "simple-bus";
>>> + ranges = <0xfffff400 0xfffff400 0xa00>;
>>> +
>>> + atmel,mux-mask = <
>>> + /* A B */
>>> + 0xffffffff 0xfffffff7 /* pioA */
>>> + 0xffffffff 0xfffffff4 /* pioB */
>>> + 0xffffffff 0xffffff07 /* pioC */
>>> + >;
>>> +
>>> + /* shared pinctrl settings */
>>> + dbgu {
>>> + pinctrl_dbgu: dbgu-0 {
>>> + atmel,pins =
>>> + <AT91_PIOA 9 AT91_PERIPH_A AT91_PINCTRL_NONE /* PA9 periph A */
>>
>> You may have to remove comments for pin descriptions. It is not needed
>> anymore with pre-processor macros.
> That is a relief
>
>>
>>> + AT91_PIOA 10 AT91_PERIPH_A AT91_PINCTRL_PULL_UP>; /* PA10 periph A with pullup */
>>> + };
>>> + };
>>> +
>>> + usart0 {
>>> + pinctrl_usart0: usart0-0 {
>>> + atmel,pins =
>>> + <AT91_PIOC 8 AT91_PERIPH_A AT91_PINCTRL_PULL_UP /* PC8 periph A with pullup */
>>> + AT91_PIOC 9 AT91_PERIPH_A AT91_PINCTRL_NONE>; /* PC9 periph A */
>>> + };
>>> +
>>> + pinctrl_usart0_rts: usart0_rts-0 {
>>> + atmel,pins =
>>> + <AT91_PIOC 10 AT91_PERIPH_A AT91_PINCTRL_NONE>; /* PC10 periph A */
>>> + };
>>> +
>>> + pinctrl_usart0_cts: usart0_cts-0 {
>>> + atmel,pins =
>>> + <AT91_PIOC 11 AT91_PERIPH_A AT91_PINCTRL_NONE>; /* PC11 periph A */
>>> + };
>>> + };
>>> +
>>> + usart1 {
>>> + pinctrl_usart1: usart1-0 {
>>> + atmel,pins =
>>> + <AT91_PIOC 12 AT91_PERIPH_A AT91_PINCTRL_PULL_UP /* PC12 periph A with pullup */
>>> + AT91_PIOC 13 AT91_PERIPH_A AT91_PINCTRL_NONE>; /* PC13 periph A */
>>> + };
>>> +
>>> + pinctrl_usart1_rts: usart1_rts-0 {
>>> + atmel,pins =
>>> + <AT91_PIOA 12 AT91_PERIPH_B AT91_PINCTRL_NONE>; /* PA12 periph B */
>>> + };
>>> +
>>> + pinctrl_usart1_cts: usart1_cts-0 {
>>> + atmel,pins =
>>> + <AT91_PIOA 13 AT91_PERIPH_B AT91_PINCTRL_NONE>; /* PA13 periph B */
>>> + };
>>> + };
>>> +
>>> + usart2 {
>>> + pinctrl_usart2: usart2-0 {
>>> + atmel,pins =
>>> + <AT91_PIOC 14 AT91_PERIPH_A AT91_PINCTRL_PULL_UP /* PC14 periph A with pullup */
>>> + AT91_PIOC 15 AT91_PERIPH_A AT91_PINCTRL_NONE>; /* PC15 periph A */
>>> + };
>>> +
>>> + pinctrl_usart2_rts: usart2_rts-0 {
>>> + atmel,pins =
>>> + <AT91_PIOA 15 AT91_PERIPH_B AT91_PINCTRL_NONE>; /* PA15 periph B */
>>> + };
>>> +
>>> + pinctrl_usart2_cts: usart2_cts-0 {
>>> + atmel,pins =
>>> + <AT91_PIOA 16 AT91_PERIPH_B AT91_PINCTRL_NONE>; /* PA16 periph B */
>>> + };
>>> + };
>>> +
>>> + nand {
>>> + pinctrl_nand: nand-0 {
>>> + atmel,pins =
>>> + <AT91_PIOC 15 AT91_PERIPH_GPIO AT91_PINCTRL_PULL_UP /* PC15 gpio RDY pin pull_up*/
>>> + AT91_PIOC 14 AT91_PERIPH_GPIO AT91_PINCTRL_PULL_UP>; /* PC14 gpio enable pin pull_up */
>>> + };
>>> + };
>>> +
>>> + mmc0 {
>>> + pinctrl_mmc0_clk: mmc0_clk-0 {
>>> + atmel,pins =
>>> + <AT91_PIOA 2 AT91_PERIPH_B AT91_PINCTRL_NONE>; /* PA2 periph B */
>>> + };
>>> +
>>> + pinctrl_mmc0_slot0_cmd_dat0: mmc0_slot0_cmd_dat0-0 {
>>> + atmel,pins =
>>> + <AT91_PIOA 1 AT91_PERIPH_B AT91_PINCTRL_PULL_UP /* PA1 periph B with pullup */
>>> + AT91_PIOA 0 AT91_PERIPH_B AT91_PINCTRL_PULL_UP>; /* PA0 periph B with pullup */
>>> + };
>>> +
>>> + pinctrl_mmc0_slot0_dat1_3: mmc0_slot0_dat1_3-0 {
>>> + atmel,pins =
>>> + <AT91_PIOA 4 AT91_PERIPH_B AT91_PINCTRL_PULL_UP /* PA4 periph B with pullup */
>>> + AT91_PIOA 5 AT91_PERIPH_B AT91_PINCTRL_PULL_UP /* PA5 periph B with pullup */
>>> + AT91_PIOA 6 AT91_PERIPH_B AT91_PINCTRL_PULL_UP>; /* PA6 periph B with pullup */
>>> + };
>>> + };
>>> +
>>> + ssc0 {
>>> + pinctrl_ssc0_tx: ssc0_tx-0 {
>>> + atmel,pins =
>>> + <AT91_PIOB 21 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB21 periph A */
>>> + AT91_PIOB 22 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB22 periph A */
>>> + AT91_PIOB 23 AT91_PERIPH_A AT91_PINCTRL_NONE>; /* PB23 periph A */
>>> + };
>>> +
>>> + pinctrl_ssc0_rx: ssc0_rx-0 {
>>> + atmel,pins =
>>> + <AT91_PIOB 24 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB24 periph A */
>>> + AT91_PIOB 25 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB25 periph A */
>>> + AT91_PIOB 26 AT91_PERIPH_A AT91_PINCTRL_NONE>; /* PB26 periph A */
>>> + };
>>> + };
>>> +
>>> + ssc1 {
>>> + pinctrl_ssc1_tx: ssc1_tx-0 {
>>> + atmel,pins =
>>> + <AT91_PIOA 17 AT91_PERIPH_B AT91_PINCTRL_NONE /* PA17 periph B */
>>> + AT91_PIOA 18 AT91_PERIPH_B AT91_PINCTRL_NONE /* PA18 periph B */
>>> + AT91_PIOA 19 AT91_PERIPH_B AT91_PINCTRL_NONE>; /* PA19 periph B */
>>> + };
>>> +
>>> + pinctrl_ssc1_rx: ssc1_rx-0 {
>>> + atmel,pins =
>>> + <AT91_PIOA 20 AT91_PERIPH_B AT91_PINCTRL_NONE /* PA20 periph B */
>>> + AT91_PIOA 21 AT91_PERIPH_B AT91_PINCTRL_NONE /* PA21 periph B */
>>> + AT91_PIOA 22 AT91_PERIPH_B AT91_PINCTRL_NONE>; /* PA22 periph B */
>>> + };
>>> + };
>>> +
>>> + spi0 {
>>> + pinctrl_spi0: spi0-0 {
>>> + atmel,pins =
>>> + <AT91_PIOA 0 AT91_PERIPH_A AT91_PINCTRL_NONE /* PA0 periph A SPI0_MISO pin */
>>> + AT91_PIOA 1 AT91_PERIPH_A AT91_PINCTRL_NONE /* PA1 periph A SPI0_MOSI pin */
>>> + AT91_PIOA 2 AT91_PERIPH_A AT91_PINCTRL_NONE>; /* PA2 periph A SPI0_SPCK pin */
>>> + };
>>> + };
>>> +
>>> + spi1 {
>>> + pinctrl_spi1: spi1-0 {
>>> + atmel,pins =
>>> + <AT91_PIOB 30 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB30 periph A SPI1_MISO pin */
>>> + AT91_PIOB 31 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB31 periph A SPI1_MOSI pin */
>>> + AT91_PIOB 29 AT91_PERIPH_A AT91_PINCTRL_NONE>; /* PB29 periph A SPI1_SPCK pin */
>>> + };
>>> + };
>>> +
>>> + tcb0 {
>>> + pinctrl_tcb0_tclk0: tcb0_tclk0-0 {
>>> + atmel,pins = <AT91_PIOC 16 AT91_PERIPH_B AT91_PINCTRL_NONE>;
>>> + };
>>> +
>>> + pinctrl_tcb0_tclk1: tcb0_tclk1-0 {
>>> + atmel,pins = <AT91_PIOC 17 AT91_PERIPH_B AT91_PINCTRL_NONE>;
>>> + };
>>> +
>>> + pinctrl_tcb0_tclk2: tcb0_tclk2-0 {
>>> + atmel,pins = <AT91_PIOC 18 AT91_PERIPH_B AT91_PINCTRL_NONE>;
>>> + };
>>> +
>>> + pinctrl_tcb0_tioa0: tcb0_tioa0-0 {
>>> + atmel,pins = <AT91_PIOC 19 AT91_PERIPH_B AT91_PINCTRL_NONE>;
>>> + };
>>> +
>>> + pinctrl_tcb0_tioa1: tcb0_tioa1-0 {
>>> + atmel,pins = <AT91_PIOC 21 AT91_PERIPH_B AT91_PINCTRL_NONE>;
>>> + };
>>> +
>>> + pinctrl_tcb0_tioa2: tcb0_tioa2-0 {
>>> + atmel,pins = <AT91_PIOC 23 AT91_PERIPH_B AT91_PINCTRL_NONE>;
>>> + };
>>> +
>>> + pinctrl_tcb0_tiob0: tcb0_tiob0-0 {
>>> + atmel,pins = <AT91_PIOC 20 AT91_PERIPH_B AT91_PINCTRL_NONE>;
>>> + };
>>> +
>>> + pinctrl_tcb0_tiob1: tcb0_tiob1-0 {
>>> + atmel,pins = <AT91_PIOC 22 AT91_PERIPH_B AT91_PINCTRL_NONE>;
>>> + };
>>> +
>>> + pinctrl_tcb0_tiob2: tcb0_tiob2-0 {
>>> + atmel,pins = <AT91_PIOC 24 AT91_PERIPH_B AT91_PINCTRL_NONE>;
>>> + };
>>> + };
>>> +
>>> + pioA: gpio@fffff400 {
>>> + compatible = "atmel,at91rm9200-gpio";
>>> + reg = <0xfffff400 0x200>;
>>> + interrupts = <2 IRQ_TYPE_LEVEL_HIGH 1>;
>>> + #gpio-cells = <2>;
>>> + gpio-controller;
>>> + interrupt-controller;
>>> + #interrupt-cells = <2>;
>>> + };
>>> +
>>> + pioB: gpio@fffff600 {
>>> + compatible = "atmel,at91rm9200-gpio";
>>> + reg = <0xfffff600 0x200>;
>>> + interrupts = <3 IRQ_TYPE_LEVEL_HIGH 1>;
>>> + #gpio-cells = <2>;
>>> + gpio-controller;
>>> + interrupt-controller;
>>> + #interrupt-cells = <2>;
>>> + };
>>> +
>>> + pioC: gpio@fffff800 {
>>> + compatible = "atmel,at91rm9200-gpio";
>>> + reg = <0xfffff800 0x200>;
>>> + interrupts = <4 IRQ_TYPE_LEVEL_HIGH 1>;
>>> + #gpio-cells = <2>;
>>> + gpio-controller;
>>> + interrupt-controller;
>>> + #interrupt-cells = <2>;
>>> + };
>>> + };
>>> +
>>> + dbgu: serial@fffff200 {
>>> + compatible = "atmel,at91sam9260-usart";
>>> + reg = <0xfffff200 0x200>;
>>> + interrupts = <1 IRQ_TYPE_LEVEL_HIGH 7>;
>>> + pinctrl-names = "default";
>>> + pinctrl-0 = <&pinctrl_dbgu>;
>>> + status = "disabled";
>>> + };
>>> +
>>> + usart0: serial@fffb0000 {
>>> + compatible = "atmel,at91sam9260-usart";
>>> + reg = <0xfffb0000 0x200>;
>>> + interrupts = <6 IRQ_TYPE_LEVEL_HIGH 5>;
>>> + atmel,use-dma-rx;
>>> + atmel,use-dma-tx;
>>> + pinctrl-names = "default";
>>> + pinctrl-0 = <&pinctrl_usart0>;
>>> + status = "disabled";
>>> + };
>>> +
>>> + usart1: serial@ffffb400 {
>>> + compatible = "atmel,at91sam9260-usart";
>>> + reg = <0xfffb4000 0x200>;
>>> + interrupts = <7 IRQ_TYPE_LEVEL_HIGH 5>;
>>> + atmel,use-dma-rx;
>>> + atmel,use-dma-tx;
>>> + pinctrl-names = "default";
>>> + pinctrl-0 = <&pinctrl_usart1>;
>>> + status = "disabled";
>>> + };
>>> +
>>> + usart2: serial@fff94000 {
>>> + compatible = "atmel,at91sam9260-usart";
>>> + reg = <0xfffb8000 0x200>;
>>> + interrupts = <8 IRQ_TYPE_LEVEL_HIGH 5>;
>>> + atmel,use-dma-rx;
>>> + atmel,use-dma-tx;
>>> + pinctrl-names = "default";
>>> + pinctrl-0 = <&pinctrl_usart2>;
>>> + status = "disabled";
>>> + };
>>> +
>>> + ssc0: ssc@fffbc000 {
>>> + compatible = "atmel,at91rm9200-ssc";
>>> + reg = <0xfffbc000 0x4000>;
>>> + interrupts = <14 IRQ_TYPE_LEVEL_HIGH 5>;
>>> + pinctrl-names = "default";
>>> + pinctrl-0 = <&pinctrl_ssc0_tx &pinctrl_ssc0_rx>;
>>> + status = "disabled";
>>> + };
>>> +
>>> + ssc1: ssc@fffc0000 {
>>> + compatible = "atmel,at91rm9200-ssc";
>>> + reg = <0xfffc0000 0x4000>;
>>> + interrupts = <15 IRQ_TYPE_LEVEL_HIGH 5>;
>>> + pinctrl-names = "default";
>>> + pinctrl-0 = <&pinctrl_ssc1_tx &pinctrl_ssc1_rx>;
>>> + status = "disabled";
>>> + };
>>> +
>>> + usb1: gadget@fffa4000 {
>>> + compatible = "atmel,at91rm9200-udc";
>>> + reg = <0xfffa4000 0x4000>;
>>> + interrupts = <10 IRQ_TYPE_LEVEL_HIGH 2>;
>>> + status = "disabled";
>>> + };
>>> +
>>> + i2c0: i2c@fffac000 {
>>> + compatible = "atmel,at91sam9261-i2c";
>>
>> isn't it "atmel,at91sam9260-i2c" ?
> I don't know. the i2c driver whows a difference between the 9260 and
> the 9261 regarding the clk_max_div. But I forgot to add the 9261 in
> the dt ids table of the twi driver.

Ok, nice. I am looking forward for reviewing your patch ;-)

>>> + reg = <0xfffac000 0x100>;
>>> + interrupts = <11 IRQ_TYPE_LEVEL_HIGH 6>;
>>> + #address-cells = <1>;
>>> + #size-cells = <0>;
>>> + status = "disabled";
>>> + };
>>> +
>>> + mmc0: mmc@fffa8000 {
>>> + compatible = "atmel,hsmci";
>>> + reg = <0xfffa8000 0x600>;
>>> + interrupts = <9 IRQ_TYPE_LEVEL_HIGH 0>;
>>> + #address-cells = <1>;
>>> + #size-cells = <0>;
>>> + status = "disabled";
>>> + };
>>> +
>>> + watchdog@fffffd40 {
>>> + compatible = "atmel,at91sam9260-wdt";
>>> + reg = <0xfffffd40 0x10>;
>>> + status = "disabled";
>>> + };
>>> +
>>> + spi0: spi@fffc8000 {
>>> + #address-cells = <1>;
>>> + #size-cells = <0>;
>>> + compatible = "atmel,at91rm9200-spi";
>>> + reg = <0xfffc8000 0x200>;
>>> + interrupts = <12 IRQ_TYPE_LEVEL_HIGH 3>;
>>> + pinctrl-names = "default";
>>> + pinctrl-0 = <&pinctrl_spi0>;
>>> + status = "disabled";
>>> + };
>>> +
>>> + spi1: spi@fffcc000 {
>>> + #address-cells = <1>;
>>> + #size-cells = <0>;
>>> + compatible = "atmel,at91rm9200-spi";
>>> + reg = <0xfffcc000 0x200>;
>>> + interrupts = <13 IRQ_TYPE_LEVEL_HIGH 3>;
>>> + pinctrl-names = "default";
>>> + pinctrl-0 = <&pinctrl_spi1>;
>>> + status = "disabled";
>>> + };
>>> + };
>>> +
>>> + nand0: nand@40000000 {
>>> + compatible = "atmel,at91rm9200-nand";
>>> + #address-cells = <1>;
>>> + #size-cells = <1>;
>>> + reg = <0x40000000 0x10000000>;
>>> + atmel,nand-addr-offset = <22>;
>>> + atmel,nand-cmd-offset = <21>;
>>> + pinctrl-names = "default";
>>> + pinctrl-0 = <&pinctrl_nand>;
>>> +
>>> + gpios = <&pioC 15 GPIO_ACTIVE_HIGH
>>> + &pioC 14 GPIO_ACTIVE_HIGH
>>> + 0
>>> + >;
>>> + status = "disabled";
>>> + };
>>> +
>>> + usb0: ohci@00500000 {
>>> + compatible = "atmel,at91rm9200-ohci", "usb-ohci";
>>> + reg = <0x00500000 0x100000>;
>>> + interrupts = <20 IRQ_TYPE_LEVEL_HIGH 2>;
>>> + status = "disabled";
>>> + };
>>> + };
>>> +
>>> + i2c@0 {
>>> + compatible = "i2c-gpio";
>>> + gpios = <&pioA 7 GPIO_ACTIVE_HIGH /* sda */
>>> + &pioA 8 GPIO_ACTIVE_HIGH /* scl */
>>> + >;
>>> + i2c-gpio,sda-open-drain;
>>> + i2c-gpio,scl-open-drain;
>>> + i2c-gpio,delay-us = <2>; /* ~100 kHz */
>>> + #address-cells = <1>;
>>> + #size-cells = <0>;
>>> + status = "disabled";
>>> + };
>>> +};
>>> diff --git a/arch/arm/mach-at91/at91sam9261.c b/arch/arm/mach-at91/at91sam9261.c
>>> index 6276b4c..200d17a 100644
>>> --- a/arch/arm/mach-at91/at91sam9261.c
>>> +++ b/arch/arm/mach-at91/at91sam9261.c
>>> @@ -189,6 +189,21 @@ static struct clk_lookup periph_clocks_lookups[] = {
>>> CLKDEV_CON_ID("pioA", &pioA_clk),
>>> CLKDEV_CON_ID("pioB", &pioB_clk),
>>> CLKDEV_CON_ID("pioC", &pioC_clk),
>>> + /* more usart lookup table for DT entries */
>>> + CLKDEV_CON_DEV_ID("usart", "fffff200.serial", &mck),
>>> + CLKDEV_CON_DEV_ID("usart", "fffb0000.serial", &usart0_clk),
>>> + CLKDEV_CON_DEV_ID("usart", "ffffb400.serial", &usart1_clk),
>>> + CLKDEV_CON_DEV_ID("usart", "fff94000.serial", &usart2_clk),
>>> + /* more tc lookup table for DT entries */
>>> + CLKDEV_CON_DEV_ID("t0_clk", "fffa0000.timer", &tc0_clk),
>>> + CLKDEV_CON_DEV_ID("hclk", "500000.ohci", &ohci_clk),
>>> + CLKDEV_CON_DEV_ID("spi_clk", "fffc8000.spi", &spi0_clk),
>>> + CLKDEV_CON_DEV_ID("spi_clk", "fffcc000.spi", &spi1_clk),
>>> + CLKDEV_CON_DEV_ID("mci_clk", "fffa8000.mmc", &mmc_clk),
>>> + CLKDEV_CON_DEV_ID(NULL, "fffac000.i2c", &twi_clk),
>>> + CLKDEV_CON_DEV_ID(NULL, "fffff400.gpio", &pioA_clk),
>>> + CLKDEV_CON_DEV_ID(NULL, "fffff600.gpio", &pioB_clk),
>>> + CLKDEV_CON_DEV_ID(NULL, "fffff800.gpio", &pioC_clk),
>>
>> Yes, this is where I would like the CCF to be implemented...
> Is it okay it CCF support comes latter or must it be part of this serie?
>
>>
>>
>>> };
>>>
>>> static struct clk_lookup usart_clocks_lookups[] = {
>>>
>>
>>
>> --
>> Nicolas Ferre
>
>


--
Nicolas Ferre

2014-01-15 10:18:29

by Nicolas Ferre

[permalink] [raw]
Subject: Re: [PATCH v2 06/12] at91: smc: Adds helper functions to validate and clip the smc timings.

On 15/01/2014 10:54, Jean-Jacques Hiblot :
> 2014/1/15 Nicolas Ferre <[email protected]>:
>> On 09/01/2014 13:31, Jean-Jacques Hiblot :
>>> This patchs implememnts 2 functions to help with the configuration of a
>>> chip-select's timing:
>>> * sam9_smc_check_cs_configuration : checks that the values would fit in the
>>> registers.
>>> * sam9_smc_clip_cs_configuration : clip the values to their maximum.
>>>
>>> Signed-off-by: Jean-Jacques Hiblot <[email protected]>
>>> ---
>>> arch/arm/mach-at91/include/mach/at91sam9_smc.h | 2 +
>>> arch/arm/mach-at91/sam9_smc.c | 77 ++++++++++++++++++++++++++
>>> 2 files changed, 79 insertions(+)
>>>
>>> diff --git a/arch/arm/mach-at91/include/mach/at91sam9_smc.h b/arch/arm/mach-at91/include/mach/at91sam9_smc.h
>>> index c3e29311..615ac56 100644
>>> --- a/arch/arm/mach-at91/include/mach/at91sam9_smc.h
>>> +++ b/arch/arm/mach-at91/include/mach/at91sam9_smc.h
>>> @@ -47,6 +47,8 @@ extern void sam9_smc_read_mode(int id, int cs, struct sam9_smc_config *config);
>>> extern void sam9_smc_write_mode(int id, int cs, struct sam9_smc_config *config);
>>> extern void sam9_smc_cs_read(void __iomem *, struct sam9_smc_config *config);
>>> extern void sam9_smc_cs_configure(void __iomem *, struct sam9_smc_config *cfg);
>>> +extern int sam9_smc_check_cs_configuration(struct sam9_smc_config *config);
>>> +extern void sam9_smc_clip_cs_configuration(struct sam9_smc_config *config);
>>> #endif
>>>
>>> #define AT91_SMC_SETUP 0x00 /* Setup Register for CS n */
>>> diff --git a/arch/arm/mach-at91/sam9_smc.c b/arch/arm/mach-at91/sam9_smc.c
>>> index d7a6156..fe3c492 100644
>>> --- a/arch/arm/mach-at91/sam9_smc.c
>>> +++ b/arch/arm/mach-at91/sam9_smc.c
>>> @@ -23,6 +23,83 @@
>>>
>>> static void __iomem *smc_base_addr[2];
>>>
>>> +static int count_trailing_zeroes(u32 x)
>>
>> Don't we have something generic for this?
>>
>> Check include/asm-generic/bitops/count_zeros.h
>
> I wonder how I could have missed this one :o)
>
>>
>>> +{
>>> + int ret = 0;
>>> + if (!(x & 0xFFFF)) {
>>> + ret += 16;
>>> + x = x >> 16;
>>> + }
>>> + if (!(x & 0xFF)) {
>>> + ret += 8;
>>> + x = x >> 8;
>>> + }
>>> + if (!(x & 0xF)) {
>>> + ret += 4;
>>> + x = x >> 4;
>>> + }
>>> + if (!(x & 0x3)) {
>>> + ret += 2;
>>> + x = x >> 2;
>>> + }
>>> + if (!(x & 0x1)) {
>>> + ret += 1;
>>> + x = x >> 1;
>>> + }
>>> + if (!(x & 0x1))
>>> + ret += 1;
>>> +
>>> + return ret;
>>> +}
>>> +
>>> +
>>> +#define __CHECK_CFG(config, x, y) do {\
>>> + if (x##_(config->y) > x) {\
>>> + pr_debug("error: %s (0x%x) is out of range\n", #y,\
>>> + config->y >> count_trailing_zeroes(x));\
>>> + return -EINVAL;\
>>> + } \
>>> + } while (0)
>>
>> I do not like the use of macro for this. You can convert them to
>> functions and it would increase readability. I am pretty confident that
>> gcc will optimize it so that is won't impact performance.
>
> It's not a matter of performance. I wanted to use the stringification
> for the debug message.

I see. It is interesting but anyway, we might keep it simple and just
flag the error with the incriminated value...

Bye,


>>> +int sam9_smc_check_cs_configuration(struct sam9_smc_config *config)
>>> +{
>>> + __CHECK_CFG(config, AT91_SMC_NWESETUP, nwe_setup);
>>> + __CHECK_CFG(config, AT91_SMC_NCS_WRSETUP, ncs_write_setup);
>>> + __CHECK_CFG(config, AT91_SMC_NRDSETUP, nrd_setup);
>>> + __CHECK_CFG(config, AT91_SMC_NCS_RDSETUP, ncs_read_setup);
>>> + __CHECK_CFG(config, AT91_SMC_NWEPULSE, nwe_pulse);
>>> + __CHECK_CFG(config, AT91_SMC_NCS_WRPULSE, ncs_write_pulse);
>>> + __CHECK_CFG(config, AT91_SMC_NRDPULSE, nrd_pulse);
>>> + __CHECK_CFG(config, AT91_SMC_NCS_RDPULSE, ncs_read_pulse);
>>> + __CHECK_CFG(config, AT91_SMC_NWECYCLE, write_cycle);
>>> + __CHECK_CFG(config, AT91_SMC_NRDCYCLE, read_cycle);
>>> + __CHECK_CFG(config, AT91_SMC_TDF, tdf_cycles);
>>> + return 0;
>>> +}
>>> +
>>> +#define __CLIP_CFG(config, x, y) do {\
>>> + if (x##_(config->y) > x) {\
>>> + config->y = x >> count_trailing_zeroes(x);\
>>> + pr_debug("clipping %s to %d\n", #y, config->y);\
>>> + } \
>>> + } while (0)
>>
>> Ditto.
>>
>>> +
>>> +void sam9_smc_clip_cs_configuration(struct sam9_smc_config *config)
>>> +{
>>> + __CLIP_CFG(config, AT91_SMC_NWESETUP, nwe_setup);
>>> + __CLIP_CFG(config, AT91_SMC_NCS_WRSETUP, ncs_write_setup);
>>> + __CLIP_CFG(config, AT91_SMC_NRDSETUP, nrd_setup);
>>> + __CLIP_CFG(config, AT91_SMC_NCS_RDSETUP, ncs_read_setup);
>>> + __CLIP_CFG(config, AT91_SMC_NWEPULSE, nwe_pulse);
>>> + __CLIP_CFG(config, AT91_SMC_NCS_WRPULSE, ncs_write_pulse);
>>> + __CLIP_CFG(config, AT91_SMC_NRDPULSE, nrd_pulse);
>>> + __CLIP_CFG(config, AT91_SMC_NCS_RDPULSE, ncs_read_pulse);
>>> + __CLIP_CFG(config, AT91_SMC_NWECYCLE, write_cycle);
>>> + __CLIP_CFG(config, AT91_SMC_NRDCYCLE, read_cycle);
>>> + __CLIP_CFG(config, AT91_SMC_TDF, tdf_cycles);
>>> +
>>> +}
>>> +
>>> static void sam9_smc_cs_write_mode(void __iomem *base,
>>> struct sam9_smc_config *config)
>>> {
>>>
>>
>>
>> --
>> Nicolas Ferre
>
>


--
Nicolas Ferre

Subject: Re: [PATCH v2 03/12] at91: dt: sam9261: Added support for the lcd display

On 18:07 Thu 09 Jan , boris brezillon wrote:
> On 09/01/2014 13:31, Jean-Jacques Hiblot wrote:
> >Signed-off-by: Jean-Jacques Hiblot <[email protected]>
> >---
> > arch/arm/boot/dts/at91sam9261.dtsi | 37 ++++++++++++++++++++++++++++++++++++-
> > arch/arm/boot/dts/at91sam9261ek.dts | 31 +++++++++++++++++++++++++++++++
> > arch/arm/mach-at91/at91sam9261.c | 1 +
> > 3 files changed, 68 insertions(+), 1 deletion(-)
> >
> >diff --git a/arch/arm/boot/dts/at91sam9261.dtsi b/arch/arm/boot/dts/at91sam9261.dtsi
> >index 773c3d6..cd219b9 100644
> >--- a/arch/arm/boot/dts/at91sam9261.dtsi
> >+++ b/arch/arm/boot/dts/at91sam9261.dtsi
> >@@ -290,7 +290,33 @@
> > atmel,pins = <AT91_PIOC 24 AT91_PERIPH_B AT91_PINCTRL_NONE>;
> > };
> > };
> >-
> >+ fb {
> >+ pinctrl_fb: fb-0 {
> >+ atmel,pins =
> >+ <AT91_PIOB 1 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB1 periph A */
> >+ AT91_PIOB 2 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB2 periph A */
> >+ AT91_PIOB 3 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB3 periph A */
> >+ AT91_PIOB 7 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB7 periph A */
> >+ AT91_PIOB 8 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB8 periph A */
> >+ AT91_PIOB 9 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB9 periph A */
> >+ AT91_PIOB 10 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB10 periph A */
> >+ AT91_PIOB 11 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB11 periph A */
> >+ AT91_PIOB 12 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB12 periph A */
> >+ AT91_PIOB 15 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB15 periph A */
> >+ AT91_PIOB 16 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB16 periph A */
> >+ AT91_PIOB 17 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB17 periph A */
> >+ AT91_PIOB 18 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB18 periph A */
> >+ AT91_PIOB 19 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB19 periph A */
> >+ AT91_PIOB 20 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB20 periph A */
> >+ AT91_PIOB 23 AT91_PERIPH_B AT91_PINCTRL_NONE /* PB23 periph B */
> >+ AT91_PIOB 24 AT91_PERIPH_B AT91_PINCTRL_NONE /* PB24 periph B */
> >+ AT91_PIOB 25 AT91_PERIPH_B AT91_PINCTRL_NONE /* PB25 periph B */
> >+ AT91_PIOB 26 AT91_PERIPH_B AT91_PINCTRL_NONE /* PB26 periph B */
> >+ AT91_PIOB 27 AT91_PERIPH_B AT91_PINCTRL_NONE /* PB27 periph B */
> >+ AT91_PIOB 28 AT91_PERIPH_B AT91_PINCTRL_NONE /* PB28 periph B */
> >+ >;
> >+ };
> >+ };
> > pioA: gpio@fffff400 {
> > compatible = "atmel,at91rm9200-gpio";
> > reg = <0xfffff400 0x200>;
> >@@ -436,6 +462,15 @@
> > };
> > };
> >+ fb0: fb@0x00600000 {
> >+ compatible = "atmel,at91sam9261-lcdc";
> >+ reg = <0x00600000 0x1000>;
> >+ interrupts = <21 IRQ_TYPE_LEVEL_HIGH 3>;
> >+ pinctrl-names = "default";
> >+ pinctrl-0 = <&pinctrl_fb>;
> >+ status = "disabled";
> >+ };
> >+
> > nand0: nand@40000000 {
> > compatible = "atmel,at91rm9200-nand";
> > #address-cells = <1>;
> >diff --git a/arch/arm/boot/dts/at91sam9261ek.dts b/arch/arm/boot/dts/at91sam9261ek.dts
> >index f3d22a9..03c05fc 100644
> >--- a/arch/arm/boot/dts/at91sam9261ek.dts
> >+++ b/arch/arm/boot/dts/at91sam9261ek.dts
> >@@ -52,6 +52,37 @@
> > reg = <0x0 0x20000>;
> > };
> > };
>
> One more nitpick :
> I think this should be taken out in another patch (first 9261 lcdc support,
> then ek board lcd def) :p.

yes mandatory

Best Regards,
J.
>
> >+
> >+ fb0: fb@0x00600000 {
> >+ display = <&display0>;
> >+ status = "okay";
> >+ atmel,power-control-gpio = <&pioA 12 GPIO_ACTIVE_LOW>;
> >+ display0: display {
> >+ bits-per-pixel = <16>;
> >+ atmel,lcdcon-backlight;
> >+ atmel,dmacon = <0x1>;
> >+ atmel,lcdcon2 = <0x80008002>;
> >+ atmel,guard-time = <1>;
> >+ atmel,lcd-wiring-mode = "BRG";
> >+
> >+ display-timings {
> >+ native-mode = <&timing0>;
> >+ timing0: timing0 {
> >+ clock-frequency = <4965000>;
> >+ hactive = <240>;
> >+ vactive = <320>;
> >+ hback-porch = <1>;
> >+ hfront-porch = <33>;
> >+ vback-porch = <1>;
> >+ vfront-porch = <0>;
> >+ hsync-len = <5>;
> >+ vsync-len = <1>;
> >+ hsync-active = <1>;
> >+ vsync-active = <1>;
> >+ };
> >+ };
> >+ };
> >+ };
> > };
> > leds {
> >diff --git a/arch/arm/mach-at91/at91sam9261.c b/arch/arm/mach-at91/at91sam9261.c
> >index 200d17a..a67bfe6 100644
> >--- a/arch/arm/mach-at91/at91sam9261.c
> >+++ b/arch/arm/mach-at91/at91sam9261.c
> >@@ -197,6 +197,7 @@ static struct clk_lookup periph_clocks_lookups[] = {
> > /* more tc lookup table for DT entries */
> > CLKDEV_CON_DEV_ID("t0_clk", "fffa0000.timer", &tc0_clk),
> > CLKDEV_CON_DEV_ID("hclk", "500000.ohci", &ohci_clk),
> >+ CLKDEV_CON_DEV_ID("hclk", "600000.fb", &hck1),
> > CLKDEV_CON_DEV_ID("spi_clk", "fffc8000.spi", &spi0_clk),
> > CLKDEV_CON_DEV_ID("spi_clk", "fffcc000.spi", &spi1_clk),
> > CLKDEV_CON_DEV_ID("mci_clk", "fffa8000.mmc", &mmc_clk),
>
>
> _______________________________________________
> linux-arm-kernel mailing list
> [email protected]
> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

Subject: Re: [PATCH v2 12/12] at91: dt: sam9261: Added DM9000 in the device tree

On 13:31 Thu 09 Jan , Jean-Jacques Hiblot wrote:
> Signed-off-by: Jean-Jacques Hiblot <[email protected]>
> ---
> arch/arm/boot/dts/at91sam9261ek.dts | 32 ++++++++++++++++++++++++++++++++
> 1 file changed, 32 insertions(+)
>
> diff --git a/arch/arm/boot/dts/at91sam9261ek.dts b/arch/arm/boot/dts/at91sam9261ek.dts
> index e92cb8e..a3d9d5a 100644
> --- a/arch/arm/boot/dts/at91sam9261ek.dts
> +++ b/arch/arm/boot/dts/at91sam9261ek.dts
> @@ -76,6 +76,38 @@
> smc: smc@ffffec00 {
> status = "okay";
>
> + ebi_cs2@2,0 {
> + status = "okay";
> + smc,converter = "nanosec";
> + smc,ncs_read_setup = <0>;
> + smc,nrd_setup = <20>;
> + smc,ncs_write_setup = <0>;
> + smc,nwe_setup = <20>;
> + smc,ncs_read_pulse = <80>;
> + smc,nrd_pulse = <40>;
> + smc,ncs_write_pulse = <80>;
> + smc,nwe_pulse = <40>;
> + smc,read_cycle = <160>;
> + smc,write_cycle = <160>;
> + smc,tdf_cycles = <10>;
> + smc,tdf_optimized = <0>;
> + smc,page_size = <0>;
> + smc,byte_access_type = <1>;
> + smc,bus_width = <1>;
> + smc,nwait_mode = <0>;
> + smc,read_mode = <1>;
> + smc,write_mode = <1>;
> +
> + ethernet@2,0 {
> + compatible = "davicom,dm9000";
> + reg = <0x0 0x2 0x4 0x2>;
> + interrupt-parent = <&pioC>;
> + interrupts = <11 IRQ_TYPE_EDGE_BOTH>;
use the new binding no interrupt-parent now
> + local-mac-address = [00 00 de ad be ef];
drop the mac this is board specific
> + davicom,no-eeprom;
> + };
> + };
> +
> ebi_cs3@3,0 {
> status = "okay";
> smc,ncs_read_setup = <0>;
> --
> 1.8.5.2
>
>
> _______________________________________________
> linux-arm-kernel mailing list:wqa

> [email protected]
> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

Subject: Re: [PATCH v2 01/12] at91: dt: Add at91sam9261 dt SoC support

On 18:01 Tue 14 Jan , Nicolas Ferre wrote:
> On 09/01/2014 13:31, Jean-Jacques Hiblot :
> > This patch adds the basics to support the Device Tree on a sam9261-based platform
> >
> > Signed-off-by: Jean-Jacques Hiblot <[email protected]>
> > ---
> > arch/arm/boot/dts/at91sam9261.dtsi | 476 +++++++++++++++++++++++++++++++++++++
> > arch/arm/mach-at91/at91sam9261.c | 15 ++
> > 2 files changed, 491 insertions(+)
> > create mode 100644 arch/arm/boot/dts/at91sam9261.dtsi
> >
> > diff --git a/arch/arm/boot/dts/at91sam9261.dtsi b/arch/arm/boot/dts/at91sam9261.dtsi
> > new file mode 100644
> > index 0000000..773c3d6
> > --- /dev/null
> > +++ b/arch/arm/boot/dts/at91sam9261.dtsi
> > @@ -0,0 +1,476 @@
> > +/*
> > + * at91sam9261.dtsi - Device Tree Include file for AT91SAM9261 SoC
> > + *
> > + * Copyright (C) 2013 Jean-Jacques Hiblot <[email protected]>
> > + *
> > + * Licensed under GPLv2 only.
> > + */
> > +
> > +#include "skeleton.dtsi"
> > +#include <dt-bindings/pinctrl/at91.h>
> > +#include <dt-bindings/interrupt-controller/irq.h>
> > +#include <dt-bindings/gpio/gpio.h>
> > +
> > +/ {
> > + model = "Atmel AT91SAM9261 family SoC";
> > + compatible = "atmel,at91sam9261";
> > + interrupt-parent = <&aic>;
> > +
> > + aliases {
> > + serial0 = &dbgu;
> > + serial1 = &usart0;
> > + serial2 = &usart1;
> > + serial3 = &usart2;
> > + gpio0 = &pioA;
> > + gpio1 = &pioB;
> > + gpio2 = &pioC;
> > + tcb0 = &tcb0;
> > + i2c0 = &i2c0;
> > + ssc0 = &ssc0;
> > + ssc1 = &ssc1;
> > + };
> > + cpus {
> > + #address-cells = <0>;
> > + #size-cells = <0>;
> > +
> > + cpu {
> > + compatible = "arm,arm926ej-s";
> > + device_type = "cpu";
> > + };
> > + };
> > +
> > + memory {
> > + reg = <0x20000000 0x08000000>;
> > + };
> > +
> > + ahb {
> > + compatible = "simple-bus";
> > + #address-cells = <1>;
> > + #size-cells = <1>;
> > + ranges;
> > +
> > + apb {
> > + compatible = "simple-bus";
> > + #address-cells = <1>;
> > + #size-cells = <1>;
> > + ranges;
>
> I know that it is not always done but can you please sort all nodes by
> ascending address order? It is always simple to deal with node additions
> when sorted this way.
>
> > +
> > + aic: interrupt-controller@fffff000 {
> > + #interrupt-cells = <3>;
> > + compatible = "atmel,at91rm9200-aic";
> > + interrupt-controller;
> > + reg = <0xfffff000 0x200>;
> > + atmel,external-irqs = <29 30 31>;
> > + };
> > +
> > + pmc: pmc@fffffc00 {
> > + compatible = "atmel,at91rm9200-pmc";
> > + reg = <0xfffffc00 0x100>;
> > + };
> > +
> > + ramc: ramc@ffffea00 {
> > + compatible = "atmel,at91sam9260-sdramc";
> > + reg = <0xffffea00 0x200>;
> > + };
> > +
> > + pit: timer@fffffd30 {
> > + compatible = "atmel,at91sam9260-pit";
> > + reg = <0xfffffd30 0xf>;
> > + interrupts = <1 IRQ_TYPE_LEVEL_HIGH 7>;
> > + };
> > +
> > + tcb0: timer@fffa0000 {
> > + compatible = "atmel,at91rm9200-tcb";
> > + reg = <0xfffa0000 0x100>;
> > + interrupts = < 17 IRQ_TYPE_LEVEL_HIGH 0
> > + 18 IRQ_TYPE_LEVEL_HIGH 0
> > + 19 IRQ_TYPE_LEVEL_HIGH 0
> > + >;
> > + status = "disabled";
> > + };
> > +
> > + rstc@fffffd00 {
> > + compatible = "atmel,at91sam9260-rstc";
> > + reg = <0xfffffd00 0x10>;
> > + };
> > +
> > + shdwc@fffffd10 {
> > + compatible = "atmel,at91sam9260-shdwc";
> > + reg = <0xfffffd10 0x10>;
> > + };
> > +
> > + pinctrl@fffff400 {
> > + #address-cells = <1>;
> > + #size-cells = <1>;
> > + compatible = "atmel,at91rm9200-pinctrl", "simple-bus";
> > + ranges = <0xfffff400 0xfffff400 0xa00>;
> > +
> > + atmel,mux-mask = <
> > + /* A B */
> > + 0xffffffff 0xfffffff7 /* pioA */
> > + 0xffffffff 0xfffffff4 /* pioB */
> > + 0xffffffff 0xffffff07 /* pioC */
> > + >;
> > +
> > + /* shared pinctrl settings */
> > + dbgu {
> > + pinctrl_dbgu: dbgu-0 {
> > + atmel,pins =
> > + <AT91_PIOA 9 AT91_PERIPH_A AT91_PINCTRL_NONE /* PA9 periph A */
>
> You may have to remove comments for pin descriptions. It is not needed
> anymore with pre-processor macros.
>
> > + AT91_PIOA 10 AT91_PERIPH_A AT91_PINCTRL_PULL_UP>; /* PA10 periph A with pullup */
> > + };
> > + };
> > +
> > + usart0 {
> > + pinctrl_usart0: usart0-0 {
> > + atmel,pins =
> > + <AT91_PIOC 8 AT91_PERIPH_A AT91_PINCTRL_PULL_UP /* PC8 periph A with pullup */
> > + AT91_PIOC 9 AT91_PERIPH_A AT91_PINCTRL_NONE>; /* PC9 periph A */
> > + };
> > +
> > + pinctrl_usart0_rts: usart0_rts-0 {
> > + atmel,pins =
> > + <AT91_PIOC 10 AT91_PERIPH_A AT91_PINCTRL_NONE>; /* PC10 periph A */
> > + };
> > +
> > + pinctrl_usart0_cts: usart0_cts-0 {
> > + atmel,pins =
> > + <AT91_PIOC 11 AT91_PERIPH_A AT91_PINCTRL_NONE>; /* PC11 periph A */
> > + };
> > + };
> > +
> > + usart1 {
> > + pinctrl_usart1: usart1-0 {
> > + atmel,pins =
> > + <AT91_PIOC 12 AT91_PERIPH_A AT91_PINCTRL_PULL_UP /* PC12 periph A with pullup */
> > + AT91_PIOC 13 AT91_PERIPH_A AT91_PINCTRL_NONE>; /* PC13 periph A */
> > + };
> > +
> > + pinctrl_usart1_rts: usart1_rts-0 {
> > + atmel,pins =
> > + <AT91_PIOA 12 AT91_PERIPH_B AT91_PINCTRL_NONE>; /* PA12 periph B */
> > + };
> > +
> > + pinctrl_usart1_cts: usart1_cts-0 {
> > + atmel,pins =
> > + <AT91_PIOA 13 AT91_PERIPH_B AT91_PINCTRL_NONE>; /* PA13 periph B */
> > + };
> > + };
> > +
> > + usart2 {
> > + pinctrl_usart2: usart2-0 {
> > + atmel,pins =
> > + <AT91_PIOC 14 AT91_PERIPH_A AT91_PINCTRL_PULL_UP /* PC14 periph A with pullup */
> > + AT91_PIOC 15 AT91_PERIPH_A AT91_PINCTRL_NONE>; /* PC15 periph A */
> > + };
> > +
> > + pinctrl_usart2_rts: usart2_rts-0 {
> > + atmel,pins =
> > + <AT91_PIOA 15 AT91_PERIPH_B AT91_PINCTRL_NONE>; /* PA15 periph B */
> > + };
> > +
> > + pinctrl_usart2_cts: usart2_cts-0 {
> > + atmel,pins =
> > + <AT91_PIOA 16 AT91_PERIPH_B AT91_PINCTRL_NONE>; /* PA16 periph B */
> > + };
> > + };
> > +
> > + nand {
> > + pinctrl_nand: nand-0 {
> > + atmel,pins =
> > + <AT91_PIOC 15 AT91_PERIPH_GPIO AT91_PINCTRL_PULL_UP /* PC15 gpio RDY pin pull_up*/
> > + AT91_PIOC 14 AT91_PERIPH_GPIO AT91_PINCTRL_PULL_UP>; /* PC14 gpio enable pin pull_up */
> > + };
> > + };
> > +
> > + mmc0 {
> > + pinctrl_mmc0_clk: mmc0_clk-0 {
> > + atmel,pins =
> > + <AT91_PIOA 2 AT91_PERIPH_B AT91_PINCTRL_NONE>; /* PA2 periph B */
> > + };
> > +
> > + pinctrl_mmc0_slot0_cmd_dat0: mmc0_slot0_cmd_dat0-0 {
> > + atmel,pins =
> > + <AT91_PIOA 1 AT91_PERIPH_B AT91_PINCTRL_PULL_UP /* PA1 periph B with pullup */
> > + AT91_PIOA 0 AT91_PERIPH_B AT91_PINCTRL_PULL_UP>; /* PA0 periph B with pullup */
> > + };
> > +
> > + pinctrl_mmc0_slot0_dat1_3: mmc0_slot0_dat1_3-0 {
> > + atmel,pins =
> > + <AT91_PIOA 4 AT91_PERIPH_B AT91_PINCTRL_PULL_UP /* PA4 periph B with pullup */
> > + AT91_PIOA 5 AT91_PERIPH_B AT91_PINCTRL_PULL_UP /* PA5 periph B with pullup */
> > + AT91_PIOA 6 AT91_PERIPH_B AT91_PINCTRL_PULL_UP>; /* PA6 periph B with pullup */
> > + };
> > + };
> > +
> > + ssc0 {
> > + pinctrl_ssc0_tx: ssc0_tx-0 {
> > + atmel,pins =
> > + <AT91_PIOB 21 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB21 periph A */
> > + AT91_PIOB 22 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB22 periph A */
> > + AT91_PIOB 23 AT91_PERIPH_A AT91_PINCTRL_NONE>; /* PB23 periph A */
> > + };
> > +
> > + pinctrl_ssc0_rx: ssc0_rx-0 {
> > + atmel,pins =
> > + <AT91_PIOB 24 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB24 periph A */
> > + AT91_PIOB 25 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB25 periph A */
> > + AT91_PIOB 26 AT91_PERIPH_A AT91_PINCTRL_NONE>; /* PB26 periph A */
> > + };
> > + };
> > +
> > + ssc1 {
> > + pinctrl_ssc1_tx: ssc1_tx-0 {
> > + atmel,pins =
> > + <AT91_PIOA 17 AT91_PERIPH_B AT91_PINCTRL_NONE /* PA17 periph B */
> > + AT91_PIOA 18 AT91_PERIPH_B AT91_PINCTRL_NONE /* PA18 periph B */
> > + AT91_PIOA 19 AT91_PERIPH_B AT91_PINCTRL_NONE>; /* PA19 periph B */
> > + };
> > +
> > + pinctrl_ssc1_rx: ssc1_rx-0 {
> > + atmel,pins =
> > + <AT91_PIOA 20 AT91_PERIPH_B AT91_PINCTRL_NONE /* PA20 periph B */
> > + AT91_PIOA 21 AT91_PERIPH_B AT91_PINCTRL_NONE /* PA21 periph B */
> > + AT91_PIOA 22 AT91_PERIPH_B AT91_PINCTRL_NONE>; /* PA22 periph B */
> > + };
> > + };
> > +
> > + spi0 {
> > + pinctrl_spi0: spi0-0 {
> > + atmel,pins =
> > + <AT91_PIOA 0 AT91_PERIPH_A AT91_PINCTRL_NONE /* PA0 periph A SPI0_MISO pin */
> > + AT91_PIOA 1 AT91_PERIPH_A AT91_PINCTRL_NONE /* PA1 periph A SPI0_MOSI pin */
> > + AT91_PIOA 2 AT91_PERIPH_A AT91_PINCTRL_NONE>; /* PA2 periph A SPI0_SPCK pin */
> > + };
> > + };
> > +
> > + spi1 {
> > + pinctrl_spi1: spi1-0 {
> > + atmel,pins =
> > + <AT91_PIOB 30 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB30 periph A SPI1_MISO pin */
> > + AT91_PIOB 31 AT91_PERIPH_A AT91_PINCTRL_NONE /* PB31 periph A SPI1_MOSI pin */
> > + AT91_PIOB 29 AT91_PERIPH_A AT91_PINCTRL_NONE>; /* PB29 periph A SPI1_SPCK pin */
> > + };
> > + };
> > +
> > + tcb0 {
> > + pinctrl_tcb0_tclk0: tcb0_tclk0-0 {
> > + atmel,pins = <AT91_PIOC 16 AT91_PERIPH_B AT91_PINCTRL_NONE>;
> > + };
> > +
> > + pinctrl_tcb0_tclk1: tcb0_tclk1-0 {
> > + atmel,pins = <AT91_PIOC 17 AT91_PERIPH_B AT91_PINCTRL_NONE>;
> > + };
> > +
> > + pinctrl_tcb0_tclk2: tcb0_tclk2-0 {
> > + atmel,pins = <AT91_PIOC 18 AT91_PERIPH_B AT91_PINCTRL_NONE>;
> > + };
> > +
> > + pinctrl_tcb0_tioa0: tcb0_tioa0-0 {
> > + atmel,pins = <AT91_PIOC 19 AT91_PERIPH_B AT91_PINCTRL_NONE>;
> > + };
> > +
> > + pinctrl_tcb0_tioa1: tcb0_tioa1-0 {
> > + atmel,pins = <AT91_PIOC 21 AT91_PERIPH_B AT91_PINCTRL_NONE>;
> > + };
> > +
> > + pinctrl_tcb0_tioa2: tcb0_tioa2-0 {
> > + atmel,pins = <AT91_PIOC 23 AT91_PERIPH_B AT91_PINCTRL_NONE>;
> > + };
> > +
> > + pinctrl_tcb0_tiob0: tcb0_tiob0-0 {
> > + atmel,pins = <AT91_PIOC 20 AT91_PERIPH_B AT91_PINCTRL_NONE>;
> > + };
> > +
> > + pinctrl_tcb0_tiob1: tcb0_tiob1-0 {
> > + atmel,pins = <AT91_PIOC 22 AT91_PERIPH_B AT91_PINCTRL_NONE>;
> > + };
> > +
> > + pinctrl_tcb0_tiob2: tcb0_tiob2-0 {
> > + atmel,pins = <AT91_PIOC 24 AT91_PERIPH_B AT91_PINCTRL_NONE>;
> > + };
> > + };
> > +
> > + pioA: gpio@fffff400 {
> > + compatible = "atmel,at91rm9200-gpio";
> > + reg = <0xfffff400 0x200>;
> > + interrupts = <2 IRQ_TYPE_LEVEL_HIGH 1>;
> > + #gpio-cells = <2>;
> > + gpio-controller;
> > + interrupt-controller;
> > + #interrupt-cells = <2>;
> > + };
> > +
> > + pioB: gpio@fffff600 {
> > + compatible = "atmel,at91rm9200-gpio";
> > + reg = <0xfffff600 0x200>;
> > + interrupts = <3 IRQ_TYPE_LEVEL_HIGH 1>;
> > + #gpio-cells = <2>;
> > + gpio-controller;
> > + interrupt-controller;
> > + #interrupt-cells = <2>;
> > + };
> > +
> > + pioC: gpio@fffff800 {
> > + compatible = "atmel,at91rm9200-gpio";
> > + reg = <0xfffff800 0x200>;
> > + interrupts = <4 IRQ_TYPE_LEVEL_HIGH 1>;
> > + #gpio-cells = <2>;
> > + gpio-controller;
> > + interrupt-controller;
> > + #interrupt-cells = <2>;
> > + };
> > + };
> > +
> > + dbgu: serial@fffff200 {
> > + compatible = "atmel,at91sam9260-usart";
> > + reg = <0xfffff200 0x200>;
> > + interrupts = <1 IRQ_TYPE_LEVEL_HIGH 7>;
> > + pinctrl-names = "default";
> > + pinctrl-0 = <&pinctrl_dbgu>;
> > + status = "disabled";
> > + };
> > +
> > + usart0: serial@fffb0000 {
> > + compatible = "atmel,at91sam9260-usart";
> > + reg = <0xfffb0000 0x200>;
> > + interrupts = <6 IRQ_TYPE_LEVEL_HIGH 5>;
> > + atmel,use-dma-rx;
> > + atmel,use-dma-tx;
> > + pinctrl-names = "default";
> > + pinctrl-0 = <&pinctrl_usart0>;
> > + status = "disabled";
> > + };
> > +
> > + usart1: serial@ffffb400 {
> > + compatible = "atmel,at91sam9260-usart";
> > + reg = <0xfffb4000 0x200>;
> > + interrupts = <7 IRQ_TYPE_LEVEL_HIGH 5>;
> > + atmel,use-dma-rx;
> > + atmel,use-dma-tx;
> > + pinctrl-names = "default";
> > + pinctrl-0 = <&pinctrl_usart1>;
> > + status = "disabled";
> > + };
> > +
> > + usart2: serial@fff94000 {
> > + compatible = "atmel,at91sam9260-usart";
> > + reg = <0xfffb8000 0x200>;
> > + interrupts = <8 IRQ_TYPE_LEVEL_HIGH 5>;
> > + atmel,use-dma-rx;
> > + atmel,use-dma-tx;
> > + pinctrl-names = "default";
> > + pinctrl-0 = <&pinctrl_usart2>;
> > + status = "disabled";
> > + };
> > +
> > + ssc0: ssc@fffbc000 {
> > + compatible = "atmel,at91rm9200-ssc";
> > + reg = <0xfffbc000 0x4000>;
> > + interrupts = <14 IRQ_TYPE_LEVEL_HIGH 5>;
> > + pinctrl-names = "default";
> > + pinctrl-0 = <&pinctrl_ssc0_tx &pinctrl_ssc0_rx>;
> > + status = "disabled";
> > + };
> > +
> > + ssc1: ssc@fffc0000 {
> > + compatible = "atmel,at91rm9200-ssc";
> > + reg = <0xfffc0000 0x4000>;
> > + interrupts = <15 IRQ_TYPE_LEVEL_HIGH 5>;
> > + pinctrl-names = "default";
> > + pinctrl-0 = <&pinctrl_ssc1_tx &pinctrl_ssc1_rx>;
> > + status = "disabled";
> > + };
> > +
> > + usb1: gadget@fffa4000 {
> > + compatible = "atmel,at91rm9200-udc";
> > + reg = <0xfffa4000 0x4000>;
> > + interrupts = <10 IRQ_TYPE_LEVEL_HIGH 2>;
> > + status = "disabled";
> > + };
> > +
> > + i2c0: i2c@fffac000 {
> > + compatible = "atmel,at91sam9261-i2c";
>
> isn't it "atmel,at91sam9260-i2c" ?
>
> > + reg = <0xfffac000 0x100>;
> > + interrupts = <11 IRQ_TYPE_LEVEL_HIGH 6>;
> > + #address-cells = <1>;
> > + #size-cells = <0>;
> > + status = "disabled";
> > + };
> > +
> > + mmc0: mmc@fffa8000 {
> > + compatible = "atmel,hsmci";
> > + reg = <0xfffa8000 0x600>;
> > + interrupts = <9 IRQ_TYPE_LEVEL_HIGH 0>;
> > + #address-cells = <1>;
> > + #size-cells = <0>;
> > + status = "disabled";
> > + };
> > +
> > + watchdog@fffffd40 {
> > + compatible = "atmel,at91sam9260-wdt";
> > + reg = <0xfffffd40 0x10>;
> > + status = "disabled";
> > + };
> > +
> > + spi0: spi@fffc8000 {
> > + #address-cells = <1>;
> > + #size-cells = <0>;
> > + compatible = "atmel,at91rm9200-spi";
> > + reg = <0xfffc8000 0x200>;
> > + interrupts = <12 IRQ_TYPE_LEVEL_HIGH 3>;
> > + pinctrl-names = "default";
> > + pinctrl-0 = <&pinctrl_spi0>;
> > + status = "disabled";
> > + };
> > +
> > + spi1: spi@fffcc000 {
> > + #address-cells = <1>;
> > + #size-cells = <0>;
> > + compatible = "atmel,at91rm9200-spi";
> > + reg = <0xfffcc000 0x200>;
> > + interrupts = <13 IRQ_TYPE_LEVEL_HIGH 3>;
> > + pinctrl-names = "default";
> > + pinctrl-0 = <&pinctrl_spi1>;
> > + status = "disabled";
> > + };
> > + };
> > +
> > + nand0: nand@40000000 {
> > + compatible = "atmel,at91rm9200-nand";
> > + #address-cells = <1>;
> > + #size-cells = <1>;
> > + reg = <0x40000000 0x10000000>;
> > + atmel,nand-addr-offset = <22>;
> > + atmel,nand-cmd-offset = <21>;
> > + pinctrl-names = "default";
> > + pinctrl-0 = <&pinctrl_nand>;
> > +
> > + gpios = <&pioC 15 GPIO_ACTIVE_HIGH
> > + &pioC 14 GPIO_ACTIVE_HIGH
> > + 0
> > + >;
> > + status = "disabled";
> > + };
> > +
> > + usb0: ohci@00500000 {
> > + compatible = "atmel,at91rm9200-ohci", "usb-ohci";
> > + reg = <0x00500000 0x100000>;
> > + interrupts = <20 IRQ_TYPE_LEVEL_HIGH 2>;
> > + status = "disabled";
> > + };
> > + };
> > +
> > + i2c@0 {
> > + compatible = "i2c-gpio";
> > + gpios = <&pioA 7 GPIO_ACTIVE_HIGH /* sda */
> > + &pioA 8 GPIO_ACTIVE_HIGH /* scl */
> > + >;
> > + i2c-gpio,sda-open-drain;
> > + i2c-gpio,scl-open-drain;
> > + i2c-gpio,delay-us = <2>; /* ~100 kHz */
> > + #address-cells = <1>;
> > + #size-cells = <0>;
> > + status = "disabled";
> > + };
> > +};
> > diff --git a/arch/arm/mach-at91/at91sam9261.c b/arch/arm/mach-at91/at91sam9261.c
> > index 6276b4c..200d17a 100644
> > --- a/arch/arm/mach-at91/at91sam9261.c
> > +++ b/arch/arm/mach-at91/at91sam9261.c
> > @@ -189,6 +189,21 @@ static struct clk_lookup periph_clocks_lookups[] = {
> > CLKDEV_CON_ID("pioA", &pioA_clk),
> > CLKDEV_CON_ID("pioB", &pioB_clk),
> > CLKDEV_CON_ID("pioC", &pioC_clk),
> > + /* more usart lookup table for DT entries */
> > + CLKDEV_CON_DEV_ID("usart", "fffff200.serial", &mck),
> > + CLKDEV_CON_DEV_ID("usart", "fffb0000.serial", &usart0_clk),
> > + CLKDEV_CON_DEV_ID("usart", "ffffb400.serial", &usart1_clk),
> > + CLKDEV_CON_DEV_ID("usart", "fff94000.serial", &usart2_clk),
> > + /* more tc lookup table for DT entries */
> > + CLKDEV_CON_DEV_ID("t0_clk", "fffa0000.timer", &tc0_clk),
> > + CLKDEV_CON_DEV_ID("hclk", "500000.ohci", &ohci_clk),
> > + CLKDEV_CON_DEV_ID("spi_clk", "fffc8000.spi", &spi0_clk),
> > + CLKDEV_CON_DEV_ID("spi_clk", "fffcc000.spi", &spi1_clk),
> > + CLKDEV_CON_DEV_ID("mci_clk", "fffa8000.mmc", &mmc_clk),
> > + CLKDEV_CON_DEV_ID(NULL, "fffac000.i2c", &twi_clk),
> > + CLKDEV_CON_DEV_ID(NULL, "fffff400.gpio", &pioA_clk),
> > + CLKDEV_CON_DEV_ID(NULL, "fffff600.gpio", &pioB_clk),
> > + CLKDEV_CON_DEV_ID(NULL, "fffff800.gpio", &pioC_clk),
>
> Yes, this is where I would like the CCF to be implemented...

all new SoC need to CCF the old implementation is just for already present SoC
in dt

and also no interrupt-parent and interrupts anymore
we use now interrupts-extended

Best Regards,
J.

Subject: Re: [PATCH v2 09/12] at91: dt: sam9261: Pinmux DT entries for the SMC/EBI interface

On 13:31 Thu 09 Jan , Jean-Jacques Hiblot wrote:
> Signed-off-by: Jean-Jacques Hiblot <[email protected]>
> ---
> arch/arm/boot/dts/at91sam9261.dtsi | 54 ++++++++++++++++++++++++++++++++++++++
> 1 file changed, 54 insertions(+)
>
> diff --git a/arch/arm/boot/dts/at91sam9261.dtsi b/arch/arm/boot/dts/at91sam9261.dtsi
> index cd219b9..695d5d2 100644
> --- a/arch/arm/boot/dts/at91sam9261.dtsi
> +++ b/arch/arm/boot/dts/at91sam9261.dtsi
> @@ -317,6 +317,60 @@
> >;
> };
> };
> + smc {
> + pinctrl_smc_nwait: smc_nwait-0 {
> + atmel,pins = <AT91_PIOC 2 AT91_PERIPH_A AT91_PINCTRL_NONE>;
> + };
> + pinctrl_smc_a23_a25: smc_a23_a25-0 {
use better name this is not clear
> + atmel,pins =
> + <AT91_PIOA 30 AT91_PERIPH_B AT91_PINCTRL_NONE
> + AT91_PIOA 31 AT91_PERIPH_B AT91_PINCTRL_NONE
> + AT91_PIOC 3 AT91_PERIPH_A AT91_PINCTRL_NONE>;
> + };
> + pinctrl_smc_d16_d31: smc_d16_d31-0 {
> + atmel,pins =
> + <AT91_PIOC 16 AT91_PERIPH_A AT91_PINCTRL_NONE
> + AT91_PIOC 17 AT91_PERIPH_A AT91_PINCTRL_NONE
> + AT91_PIOC 18 AT91_PERIPH_A AT91_PINCTRL_NONE
> + AT91_PIOC 19 AT91_PERIPH_A AT91_PINCTRL_NONE
> + AT91_PIOC 20 AT91_PERIPH_A AT91_PINCTRL_NONE
> + AT91_PIOC 21 AT91_PERIPH_A AT91_PINCTRL_NONE
> + AT91_PIOC 22 AT91_PERIPH_A AT91_PINCTRL_NONE
> + AT91_PIOC 23 AT91_PERIPH_A AT91_PINCTRL_NONE
> + AT91_PIOC 24 AT91_PERIPH_A AT91_PINCTRL_NONE
> + AT91_PIOC 25 AT91_PERIPH_A AT91_PINCTRL_NONE
> + AT91_PIOC 26 AT91_PERIPH_A AT91_PINCTRL_NONE
> + AT91_PIOC 27 AT91_PERIPH_A AT91_PINCTRL_NONE
> + AT91_PIOC 28 AT91_PERIPH_A AT91_PINCTRL_NONE
> + AT91_PIOC 29 AT91_PERIPH_A AT91_PINCTRL_NONE
> + AT91_PIOC 30 AT91_PERIPH_A AT91_PINCTRL_NONE
> + AT91_PIOC 31 AT91_PERIPH_A AT91_PINCTRL_NONE>;
> + };
> + pinctrl_smc_ncs4: smc_ncs4-0 {
> + atmel,pins = <AT91_PIOC 4 AT91_PERIPH_A AT91_PINCTRL_NONE>;
> + };
> + pinctrl_smc_ncs5: smc_ncs5-0 {
> + atmel,pins = <AT91_PIOC 5 AT91_PERIPH_A AT91_PINCTRL_NONE>;
> + };
> + pinctrl_smc_nandoe: smc_nandoe-0 {
> + atmel,pins = <AT91_PIOC 0 AT91_PERIPH_A AT91_PINCTRL_NONE>;
> + };
> + pinctrl_smc_ncs6: smc_ncs6-0 {
> + atmel,pins = <AT91_PIOC 0 AT91_PERIPH_B AT91_PINCTRL_NONE>;
> + };
> + pinctrl_smc_nandwe: smc_nandwe-0 {
> + atmel,pins = <AT91_PIOC 1 AT91_PERIPH_A AT91_PINCTRL_NONE>;
> + };
> + pinctrl_smc_ncs7: smc_ncs7-0 {
> + atmel,pins = <AT91_PIOC 1 AT91_PERIPH_B AT91_PINCTRL_NONE>;
> + };
> + pinctrl_smc_cfce1: smc_cfce1-0 {
> + atmel,pins = <AT91_PIOC 6 AT91_PERIPH_A AT91_PINCTRL_NONE>;
> + };
> + pinctrl_smc_cfce2: smc_cfce2-0 {
> + atmel,pins = <AT91_PIOC 7 AT91_PERIPH_A AT91_PINCTRL_NONE>;
> + };
> + };
> pioA: gpio@fffff400 {
> compatible = "atmel,at91rm9200-gpio";
> reg = <0xfffff400 0x200>;
> --
> 1.8.5.2
>
>
> _______________________________________________
> linux-arm-kernel mailing list
> [email protected]
> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

Subject: Re: [PATCH v2 07/12] at91: dt: smc: Added smc bus driver

On 13:31 Thu 09 Jan , Jean-Jacques Hiblot wrote:
> The EBI/SMC external interface is used to access external peripherals (NAND
> and Ethernet controller in the case of sam9261ek). Different configurations and
> timings are required for those peripherals. This bus driver can be used to
> setup the bus timings/configuration from the device tree.
> It currently accepts timings in clock period and in nanoseconds.
>
> Signed-off-by: Jean-Jacques Hiblot <[email protected]>
> ---

NACK on the binding

will comment on the patch

Best Regards,
J.
> drivers/memory/Kconfig | 10 ++
> drivers/memory/Makefile | 1 +
> drivers/memory/atmel-smc.c | 431 +++++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 442 insertions(+)
> create mode 100644 drivers/memory/atmel-smc.c
>
> diff --git a/drivers/memory/Kconfig b/drivers/memory/Kconfig
> index 29a11db..fbdfd63 100644
> --- a/drivers/memory/Kconfig
> +++ b/drivers/memory/Kconfig
> @@ -50,4 +50,14 @@ config TEGRA30_MC
> analysis, especially for IOMMU/SMMU(System Memory Management
> Unit) module.
>
> +config ATMEL_SMC
> + bool "Atmel SMC/EBI driver"
> + default y
> + depends on SOC_AT91SAM9 && OF
> + help
> + Driver for Atmel SMC/EBI controller.
> + Used to configure the EBI (external bus interface) when the device-
> + tree is used. This bus supports NANDs, external ethernet controller,
> + SRAMs, ATA devices, etc.
> +
> endif
> diff --git a/drivers/memory/Makefile b/drivers/memory/Makefile
> index 969d923..101abc4 100644
> --- a/drivers/memory/Makefile
> +++ b/drivers/memory/Makefile
> @@ -9,3 +9,4 @@ obj-$(CONFIG_TI_EMIF) += emif.o
> obj-$(CONFIG_MVEBU_DEVBUS) += mvebu-devbus.o
> obj-$(CONFIG_TEGRA20_MC) += tegra20-mc.o
> obj-$(CONFIG_TEGRA30_MC) += tegra30-mc.o
> +obj-$(CONFIG_ATMEL_SMC) += atmel-smc.o
> diff --git a/drivers/memory/atmel-smc.c b/drivers/memory/atmel-smc.c
> new file mode 100644
> index 0000000..0a1d9ba
> --- /dev/null
> +++ b/drivers/memory/atmel-smc.c
> @@ -0,0 +1,431 @@
> +/*
> + * EBI driver for Atmel SAM9 chips
> + * inspired by the fsl weim bus driver
> + *
> + * Copyright (C) 2013 JJ Hiblot.
> + *
> + * This file is licensed under the terms of the GNU General Public
> + * License version 2. This program is licensed "as is" without any
> + * warranty of any kind, whether express or implied.
> + */
> +#include <linux/module.h>
> +#include <linux/clk.h>
> +#include <linux/io.h>
> +#include <linux/of_device.h>
> +#include <mach/at91sam9_smc.h>
> +
> +struct smc_data {
> + struct clk *bus_clk;
> + void __iomem *base;
> + struct device *dev;
> +};
> +
> +struct at91_smc_devtype {
> + unsigned int cs_count;
> +};
> +
> +static const struct at91_smc_devtype sam9261_smc_devtype = {
> + .cs_count = 6,
> +};
> +
> +static const struct of_device_id smc_id_table[] = {
> + { .compatible = "atmel,at91sam9261-smc", .data = &sam9261_smc_devtype},
> + { }
> +};
> +MODULE_DEVICE_TABLE(of, smc_id_table);
> +
> +struct smc_parameters_type {
> + const char *name;
> + u16 width;
> + u16 shift;
> +};
> +
> +static const struct smc_parameters_type smc_parameters[] = {
> + {"smc,burst_size", 2, 28},
> + {"smc,burst_enabled", 1, 24},
> + {"smc,tdf_mode", 1, 20},
> + {"smc,bus_width", 2, 12},
> + {"smc,byte_access_type", 1, 8},
> + {"smc,nwait_mode", 2, 4},
> + {"smc,write_mode", 1, 0},
> + {"smc,read_mode", 1, 1},
> + {NULL}
> +};
> +
> +static int get_mode_register_from_dt(struct smc_data *smc,
> + struct device_node *np,
> + struct sam9_smc_config *cfg)
> +{
> + int ret;
> + u32 val;
> + struct device *dev = smc->dev;
> + const struct smc_parameters_type *p = smc_parameters;
> + u32 mode = cfg->mode;
> +
> + while (p->name) {
> + ret = of_property_read_u32(np, p->name , &val);
> + if (ret == -EINVAL) {
> + dev_dbg(dev, "%s: property %s not set.\n", np->name,
> + p->name);
> + p++;
> + continue;
> + } else if (ret) {
> + dev_err(dev, "%s: can't get property %s.\n", np->name,
> + p->name);
> + return ret;
> + }
> + if (val >= (1<<p->width)) {
> + dev_err(dev, "%s: property %s out of range.\n",
> + np->name, p->name);
> + return -ERANGE;
> + }
> + mode &= ~(((1<<p->width)-1) << p->shift);
> + mode |= (val << p->shift);
> + p++;
> + }
> + cfg->mode = mode;
> + return 0;
> +}
> +
> +static int generic_timing_from_dt(struct smc_data *smc, struct device_node *np,
> + struct sam9_smc_config *cfg)
> +{
> + u32 val;
> +
> + if (!of_property_read_u32(np, "smc,ncs_read_setup" , &val))
> + cfg->ncs_read_setup = val;
> +
> + if (!of_property_read_u32(np, "smc,nrd_setup" , &val))
> + cfg->nrd_setup = val;
> +
> + if (!of_property_read_u32(np, "smc,ncs_write_setup" , &val))
> + cfg->ncs_write_setup = val;
> +
> + if (!of_property_read_u32(np, "smc,nwe_setup" , &val))
> + cfg->nwe_setup = val;
> +
> + if (!of_property_read_u32(np, "smc,ncs_read_pulse" , &val))
> + cfg->ncs_read_pulse = val;
> +
> + if (!of_property_read_u32(np, "smc,nrd_pulse" , &val))
> + cfg->nrd_pulse = val;
> +
> + if (!of_property_read_u32(np, "smc,ncs_write_pulse" , &val))
> + cfg->ncs_write_pulse = val;
> +
> + if (!of_property_read_u32(np, "smc,nwe_pulse" , &val))
> + cfg->nwe_pulse = val;
> +
> + if (!of_property_read_u32(np, "smc,read_cycle" , &val))
> + cfg->read_cycle = val;
> +
> + if (!of_property_read_u32(np, "smc,write_cycle" , &val))
> + cfg->write_cycle = val;
> +
> + if (!of_property_read_u32(np, "smc,tdf_cycles" , &val))
> + cfg->tdf_cycles = val;
> +
> + return get_mode_register_from_dt(smc, np, cfg);
> +}
> +
> +/* convert the time in ns in a number of clock cycles */
> +static u32 ns_to_cycles(u32 ns, u32 clk)
> +{
> + /*
> + * convert the clk to kHz for the rest of the calculation to avoid
> + * overflow
> + */
> + u32 clk_kHz = clk / 1000;
> + u32 ncycles = ((ns * clk_kHz) + 1000000 - 1) / 1000000;
> + return ncycles;
> +}
> +
> +static u32 cycles_to_coded_cycle(u32 cycles, int a, int b)
> +{
> + u32 mask_high = (1 << a) - 1;
> + u32 mask_low = (1 << b) - 1;
> + u32 coded;
> +
> + /* check if the value can be described with the coded format */
> + if (cycles & (mask_high & ~mask_low)) {
> + /* not representable. we need to round up */
> + cycles |= mask_high;
> + cycles += 1;
> + }
> + /* Now the value can be represented in the coded format */
> + coded = (cycles & ~mask_high) >> (a - b);
> + coded |= (cycles & mask_low);
> + return coded;
> +}
> +
> +static u32 ns_to_rw_cycles(u32 ns, u32 clk)
> +{
> + return cycles_to_coded_cycle(ns_to_cycles(ns, clk), 8, 7);
> +}
> +
> +static u32 ns_to_pulse_cycles(u32 ns, u32 clk)
> +{
> + return cycles_to_coded_cycle(ns_to_cycles(ns, clk), 8, 6);
> +}
> +
> +static u32 ns_to_setup_cycles(u32 ns, u32 clk)
> +{
> + return cycles_to_coded_cycle(ns_to_cycles(ns, clk), 7, 5);
> +}
> +
> +static u32 cycles_to_ns(u32 cycles, u32 clk)
> +{
> + /*
> + * convert the clk to kHz for the rest of the calculation to avoid
> + * overflow
> + */
> + u32 clk_kHz = clk / 1000;
> + return (cycles * 1000000) / clk_kHz;
> +}
> +
> +static u32 coded_cycle_to_cycles(u32 coded, int a, int b)
> +{
> + u32 cycles = (coded >> b) << a;
> + u32 mask_low = (1 << b) - 1;
> + cycles |= (coded & mask_low);
> + return cycles;
> +}
> +
> +static u32 rw_cycles_to_ns(u32 reg, u32 clk)
> +{
> + return cycles_to_ns(coded_cycle_to_cycles(reg, 8, 7), clk);
> +}
> +
> +static u32 pulse_cycles_to_ns(u32 reg, u32 clk)
> +{
> + return cycles_to_ns(coded_cycle_to_cycles(reg, 8, 6), clk);
> +}
> +
> +static u32 setup_cycles_to_ns(u32 reg, u32 clk)
> +{
> + return cycles_to_ns(coded_cycle_to_cycles(reg, 7, 5), clk);
> +}
> +
> +static void dump_timing(struct smc_data *smc, struct sam9_smc_config *config)
> +{
> + u32 freq = clk_get_rate(smc->bus_clk);
> + struct device *dev = smc->dev;
> +
> +#define DUMP(fn, y) dev_info(dev, "%s : 0x%x (%d ns)\n", #y, config->y,\
> + fn(config->y, freq))
> +#define DUMP_PULSE(y) DUMP(pulse_cycles_to_ns, y)
> +#define DUMP_RWCYCLE(y) DUMP(rw_cycles_to_ns, y)
> +#define DUMP_SETUP(y) DUMP(setup_cycles_to_ns, y)
> +#define DUMP_SIMPLE(y) DUMP(cycles_to_ns, y)
> +
> + DUMP_SETUP(nwe_setup);
> + DUMP_SETUP(ncs_write_setup);
> + DUMP_SETUP(nrd_setup);
> + DUMP_SETUP(ncs_read_setup);
> + DUMP_PULSE(nwe_pulse);
> + DUMP_PULSE(ncs_write_pulse);
> + DUMP_PULSE(nrd_pulse);
> + DUMP_PULSE(ncs_read_pulse);
> + DUMP_RWCYCLE(write_cycle);
> + DUMP_RWCYCLE(read_cycle);
> + DUMP_SIMPLE(tdf_cycles);
> +}
> +
> +static int ns_timing_from_dt(struct smc_data *smc, struct device_node *np,
> + struct sam9_smc_config *cfg)
> +{
> + u32 t_ns;
> + u32 freq = clk_get_rate(smc->bus_clk);
> +
> + if (!of_property_read_u32(np, "smc,ncs_read_setup" , &t_ns))
> + cfg->ncs_read_setup = ns_to_setup_cycles(t_ns, freq);
> +
> + if (!of_property_read_u32(np, "smc,nrd_setup" , &t_ns))
> + cfg->nrd_setup = ns_to_setup_cycles(t_ns, freq);
> +
> + if (!of_property_read_u32(np, "smc,ncs_write_setup" , &t_ns))
> + cfg->ncs_write_setup = ns_to_setup_cycles(t_ns, freq);
> +
> + if (!of_property_read_u32(np, "smc,nwe_setup" , &t_ns))
> + cfg->nwe_setup = ns_to_setup_cycles(t_ns, freq);
> +
> + if (!of_property_read_u32(np, "smc,ncs_read_pulse" , &t_ns))
> + cfg->ncs_read_pulse = ns_to_pulse_cycles(t_ns, freq);
> +
> + if (!of_property_read_u32(np, "smc,nrd_pulse" , &t_ns))
> + cfg->nrd_pulse = ns_to_pulse_cycles(t_ns, freq);
> +
> + if (!of_property_read_u32(np, "smc,ncs_write_pulse" , &t_ns))
> + cfg->ncs_write_pulse = ns_to_pulse_cycles(t_ns, freq);
> +
> + if (!of_property_read_u32(np, "smc,nwe_pulse" , &t_ns))
> + cfg->nwe_pulse = ns_to_pulse_cycles(t_ns, freq);
> +
> + if (!of_property_read_u32(np, "smc,read_cycle" , &t_ns))
> + cfg->read_cycle = ns_to_rw_cycles(t_ns, freq);
> +
> + if (!of_property_read_u32(np, "smc,write_cycle" , &t_ns))
> + cfg->write_cycle = ns_to_rw_cycles(t_ns, freq);
> +
> + if (!of_property_read_u32(np, "smc,tdf_cycles" , &t_ns))
> + cfg->tdf_cycles = ns_to_cycles(t_ns, freq);
> +
> + return get_mode_register_from_dt(smc, np, cfg);
> +}
> +
> +struct converter {
> + const char *name;
> + int (*fn) (struct smc_data *smc, struct device_node *np,
> + struct sam9_smc_config *cfg);
> +};
> +static const struct converter converters[] = {
> + {"raw", generic_timing_from_dt},
> + {"nanosec", ns_timing_from_dt},
> +};
> +
> +/* Parse and set the timing for this device. */
> +static int smc_timing_setup(struct smc_data *smc, struct device_node *np,
> + const struct at91_smc_devtype *devtype)
> +{
> + int ret;
> + u32 cs;
> + int i;
> + struct device *dev = smc->dev;
> + const struct converter *converter;
> + const char *converter_name = NULL;
> + struct sam9_smc_config cfg;
> +
> + ret = of_property_read_u32(np, "smc,cs" , &cs);
> + if (ret < 0) {
> + dev_err(dev, "missing mandatory property : smc,cs\n");
> + return ret;
> + }
> + if (cs >= devtype->cs_count) {
> + dev_err(dev, "invalid value for property smc,cs (=%d)."
> + "Must be in range 0 to %d\n", cs, devtype->cs_count-1);
> + return -EINVAL;
> + }
> +
> + of_property_read_string(np, "smc,converter", &converter_name);
> + if (converter_name) {
> + for (i = 0; i < ARRAY_SIZE(converters); i++)
> + if (strcmp(converters[i].name, converter_name) == 0)
> + converter = &converters[i];
> + if (!converter) {
> + dev_info(dev, "unknown converter. aborting\n");
> + return -EINVAL;
> + }
> + } else {
> + dev_dbg(dev, "cs %d: no smc converter provided. using "
> + "raw register values\n", cs);
> + converter = &converters[0];
> + }
> + dev_dbg(dev, "cs %d using converter : %s\n", cs, converter->name);
> + sam9_smc_cs_read(smc->base + (0x10 * cs), &cfg);
> + converter->fn(smc, np, &cfg);
> + ret = sam9_smc_check_cs_configuration(&cfg);
> + if (ret < 0) {
> + dev_info(dev, "invalid smc configuration for cs %d."
> + "clipping values\n", cs);
> + sam9_smc_clip_cs_configuration(&cfg);
> + dump_timing(smc, &cfg);
> + }
> +#ifdef DEBUG
> + else
> + dump_timing(smc, &cfg);
> +#endif
> +
> + sam9_smc_cs_configure(smc->base + (0x10 * cs), &cfg);
> + return 0;
> +}
> +
> +static int smc_parse_dt(struct smc_data *smc)
> +{
> + struct device *dev = smc->dev;
> + const struct of_device_id *of_id = of_match_device(smc_id_table, dev);
> + const struct at91_smc_devtype *devtype = of_id->data;
> + struct device_node *child;
> + int ret;
> +
> + for_each_child_of_node(dev->of_node, child) {
> + if (!child->name)
> + continue;
> + if (!of_device_is_available(child))
> + continue;
> + ret = smc_timing_setup(smc, child, devtype);
> + if (ret) {
> + static struct property status = {
> + .name = "status",
> + .value = "disabled",
> + .length = sizeof("disabled"),
> + };
> + dev_err(dev, "%s set timing failed. This node will be disabled.\n",
> + child->full_name);
> + ret = of_update_property(child, &status);
> + if (ret < 0) {
> + dev_err(dev, "can't disable %s. aborting probe\n",
> + child->full_name);
> + break;
> + }
> + }
> + }
> +
> + ret = of_platform_populate(dev->of_node, of_default_bus_match_table,
> + NULL, dev);
> + if (ret)
> + dev_err(dev, "%s fail to create devices.\n",
> + dev->of_node->full_name);
> +
> + return ret;
> +}
> +
> +static int smc_probe(struct platform_device *pdev)
> +{
> + struct resource *res;
> + int ret;
> + void __iomem *base;
> + struct clk *clk;
> + struct smc_data *smc = devm_kzalloc(&pdev->dev, sizeof(struct smc_data),
> + GFP_KERNEL);
> +
> + if (!smc)
> + return -ENOMEM;
> +
> + smc->dev = &pdev->dev;
> +
> + /* get the resource */
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + base = devm_request_and_ioremap(&pdev->dev, res);
> + if (IS_ERR(base)) {
> + dev_err(&pdev->dev, "can't map SMC base address\n");
> + return PTR_ERR(base);
> + }
> +
> + /* get the clock */
> + clk = devm_clk_get(&pdev->dev, "smc");
> + if (IS_ERR(clk))
> + return PTR_ERR(clk);
> +
> + smc->bus_clk = clk;
> + smc->base = base;
> +
> + /* parse the device node */
> + ret = smc_parse_dt(smc);
> + if (!ret)
> + dev_info(&pdev->dev, "Driver registered.\n");
> +
> + return ret;
> +}
> +
> +static struct platform_driver smc_driver = {
> + .driver = {
> + .name = "atmel-smc",
> + .owner = THIS_MODULE,
> + .of_match_table = smc_id_table,
> + },
> +};
> +module_platform_driver_probe(smc_driver, smc_probe);
> +
> +MODULE_AUTHOR("JJ Hiblot");
> +MODULE_DESCRIPTION("Atmel's SMC/EBI driver");
> +MODULE_LICENSE("GPL");
> --
> 1.8.5.2
>
>
> _______________________________________________
> linux-arm-kernel mailing list
> [email protected]
> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel