* Re: [PATCH v3 4/4] soc: amlogic: meson-ee-pwrc: add support for the Meson GX SoCs
From: Neil Armstrong @ 2020-05-13 14:19 UTC (permalink / raw)
To: Martin Blumenstingl, robh+dt, khilman, linux-amlogic, devicetree
Cc: linux-arm-kernel, linux-kernel
In-Reply-To: <20200420202612.369370-5-martin.blumenstingl@googlemail.com>
On 20/04/2020 22:26, Martin Blumenstingl wrote:
> Add support for the Meson GX SoCs to the meson-ee-pwrc driver.
>
> The power domains on the GX SoCs are very similar to G12A. The only
> known differences so far are:
> - The GX SoCs do not have the HHI_VPU_MEM_PD_REG2 register (for the
> VPU power-domain)
> - The GX SoCs have an additional reset line called "dvin"
>
> Signed-off-by: Martin Blumenstingl <martin.blumenstingl@googlemail.com>
> ---
> drivers/soc/amlogic/meson-ee-pwrc.c | 26 ++++++++++++++++++++++++--
> 1 file changed, 24 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/soc/amlogic/meson-ee-pwrc.c b/drivers/soc/amlogic/meson-ee-pwrc.c
> index b30868da456a..a90572cb9c82 100644
> --- a/drivers/soc/amlogic/meson-ee-pwrc.c
> +++ b/drivers/soc/amlogic/meson-ee-pwrc.c
> @@ -16,6 +16,7 @@
> #include <linux/clk.h>
> #include <dt-bindings/power/meson8-power.h>
> #include <dt-bindings/power/meson-g12a-power.h>
> +#include <dt-bindings/power/meson-gxbb-power.h>
> #include <dt-bindings/power/meson-sm1-power.h>
>
> /* AO Offsets */
> @@ -73,7 +74,7 @@ struct meson_ee_pwrc_domain_data {
>
> /* TOP Power Domains */
>
> -static struct meson_ee_pwrc_top_domain g12a_pwrc_vpu = {
> +static struct meson_ee_pwrc_top_domain gxbb_pwrc_vpu = {
I thing it could be renamed gx_pwrc_vpu, but it's not a strong one
> .sleep_reg = AO_RTI_GEN_PWR_SLEEP0,
> .sleep_mask = BIT(8),
> .iso_reg = AO_RTI_GEN_PWR_SLEEP0,
> @@ -138,6 +139,12 @@ static struct meson_ee_pwrc_mem_domain g12a_pwrc_mem_vpu[] = {
> VPU_HHI_MEMPD(HHI_MEM_PD_REG0),
> };
>
> +static struct meson_ee_pwrc_mem_domain gxbb_pwrc_mem_vpu[] = {
> + VPU_MEMPD(HHI_VPU_MEM_PD_REG0),
> + VPU_MEMPD(HHI_VPU_MEM_PD_REG1),
> + VPU_HHI_MEMPD(HHI_MEM_PD_REG0),
> +};
> +
> static struct meson_ee_pwrc_mem_domain meson8_pwrc_mem_eth[] = {
> { HHI_MEM_PD_REG0, GENMASK(3, 2) },
> };
> @@ -223,11 +230,17 @@ static struct meson_ee_pwrc_mem_domain sm1_pwrc_mem_audio[] = {
> static bool pwrc_ee_get_power(struct meson_ee_pwrc_domain *pwrc_domain);
>
> static struct meson_ee_pwrc_domain_desc g12a_pwrc_domains[] = {
> - [PWRC_G12A_VPU_ID] = VPU_PD("VPU", &g12a_pwrc_vpu, g12a_pwrc_mem_vpu,
> + [PWRC_G12A_VPU_ID] = VPU_PD("VPU", &gxbb_pwrc_vpu, g12a_pwrc_mem_vpu,
> pwrc_ee_get_power, 11, 2),
> [PWRC_G12A_ETH_ID] = MEM_PD("ETH", meson8_pwrc_mem_eth),
> };
>
> +static struct meson_ee_pwrc_domain_desc gxbb_pwrc_domains[] = {
> + [PWRC_GXBB_VPU_ID] = VPU_PD("VPU", &gxbb_pwrc_vpu, gxbb_pwrc_mem_vpu,
> + pwrc_ee_get_power, 12, 2),
> + [PWRC_GXBB_ETHERNET_MEM_ID] = MEM_PD("ETH", meson8_pwrc_mem_eth),
> +};
> +
> static struct meson_ee_pwrc_domain_desc meson8_pwrc_domains[] = {
> [PWRC_MESON8_VPU_ID] = VPU_PD("VPU", &meson8_pwrc_vpu,
> meson8_pwrc_mem_vpu, pwrc_ee_get_power,
> @@ -514,6 +527,11 @@ static struct meson_ee_pwrc_domain_data meson_ee_g12a_pwrc_data = {
> .domains = g12a_pwrc_domains,
> };
>
> +static struct meson_ee_pwrc_domain_data meson_ee_gxbb_pwrc_data = {
> + .count = ARRAY_SIZE(gxbb_pwrc_domains),
> + .domains = gxbb_pwrc_domains,
> +};
> +
> static struct meson_ee_pwrc_domain_data meson_ee_m8_pwrc_data = {
> .count = ARRAY_SIZE(meson8_pwrc_domains),
> .domains = meson8_pwrc_domains,
> @@ -542,6 +560,10 @@ static const struct of_device_id meson_ee_pwrc_match_table[] = {
> .compatible = "amlogic,meson8m2-pwrc",
> .data = &meson_ee_m8b_pwrc_data,
> },
> + {
> + .compatible = "amlogic,meson-gxbb-pwrc",
> + .data = &meson_ee_gxbb_pwrc_data,
> + },
> {
> .compatible = "amlogic,meson-g12a-pwrc",
> .data = &meson_ee_g12a_pwrc_data,
>
Reviewed-by: Neil Armstrong <narmstrong@baylibre.com>
^ permalink raw reply
* Re: [PATCH v3 3/4] soc: amlogic: meson-ee-pwrc: add support for Meson8/Meson8b/Meson8m2
From: Neil Armstrong @ 2020-05-13 14:17 UTC (permalink / raw)
To: Martin Blumenstingl, robh+dt, khilman, linux-amlogic, devicetree
Cc: linux-arm-kernel, linux-kernel
In-Reply-To: <20200420202612.369370-4-martin.blumenstingl@googlemail.com>
On 20/04/2020 22:26, Martin Blumenstingl wrote:
> This adds support for the power domains on Meson8/Meson8b/Meson8m2.
> Meson8 doesn't use any reset lines while Meson8b and Meson8m2 use the
> same set of reset lines (which is different from the newer SoCs).
> Add dedicated compatible strings for Meson8, Meson8b and Meson8m2 to
> support these differences.
>
> Notable differences between Meson8 and G12A are:
> - there is no HHI_VPU_MEM_PD_REG2 on the 32-bit SoCs
> - the Meson8b datasheet describes an "audio DSP memory" power domain
> which is used for the hardware audio decoder
> - the "amlogic,ao-sysctrl" only includes the power management related
> registers on the 32-bit SoCs, meaning the for example the
> AO_RTI_GEN_PWR_SLEEP0 register is at offset (0x2 << 2) rather than
> (0x3a << 2). As result of this (0x38 << 2) is subtracted from the
> register offsets, which is the start of the power management related
> registers.
>
> Signed-off-by: Martin Blumenstingl <martin.blumenstingl@googlemail.com>
> ---
> drivers/soc/amlogic/meson-ee-pwrc.c | 72 +++++++++++++++++++++++++++--
> 1 file changed, 69 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/soc/amlogic/meson-ee-pwrc.c b/drivers/soc/amlogic/meson-ee-pwrc.c
> index 3f0261d53ad9..b30868da456a 100644
> --- a/drivers/soc/amlogic/meson-ee-pwrc.c
> +++ b/drivers/soc/amlogic/meson-ee-pwrc.c
> @@ -14,6 +14,7 @@
> #include <linux/reset-controller.h>
> #include <linux/reset.h>
> #include <linux/clk.h>
> +#include <dt-bindings/power/meson8-power.h>
> #include <dt-bindings/power/meson-g12a-power.h>
> #include <dt-bindings/power/meson-sm1-power.h>
>
> @@ -22,6 +23,12 @@
> #define AO_RTI_GEN_PWR_SLEEP0 (0x3a << 2)
> #define AO_RTI_GEN_PWR_ISO0 (0x3b << 2)
>
> +/*
> + * Meson8/Meson8b/Meson8m2 only expose the power management registers of
> + * the AO-bus as syscon. Above register offsets need to subtract this offset.
> + */
> +#define AO_RTI_GEN_MESON8_PMU_OFFSET (0x38 << 2)
This is weird, I would directly add the MESON8 PWR_SLEEP0 offset directly.
> +
> /* HHI Offsets */
>
> #define HHI_MEM_PD_REG0 (0x40 << 2)
> @@ -73,6 +80,13 @@ static struct meson_ee_pwrc_top_domain g12a_pwrc_vpu = {
> .iso_mask = BIT(9),
> };
>
> +static struct meson_ee_pwrc_top_domain meson8_pwrc_vpu = {
> + .sleep_reg = AO_RTI_GEN_PWR_SLEEP0 - AO_RTI_GEN_MESON8_PMU_OFFSET,
> + .sleep_mask = BIT(8),
> + .iso_reg = AO_RTI_GEN_PWR_SLEEP0 - AO_RTI_GEN_MESON8_PMU_OFFSET,
> + .iso_mask = BIT(9),
> +};
> +
> #define SM1_EE_PD(__bit) \
> { \
> .sleep_reg = AO_RTI_GEN_PWR_SLEEP0, \
> @@ -124,10 +138,20 @@ static struct meson_ee_pwrc_mem_domain g12a_pwrc_mem_vpu[] = {
> VPU_HHI_MEMPD(HHI_MEM_PD_REG0),
> };
>
> -static struct meson_ee_pwrc_mem_domain g12a_pwrc_mem_eth[] = {
> +static struct meson_ee_pwrc_mem_domain meson8_pwrc_mem_eth[] = {
> { HHI_MEM_PD_REG0, GENMASK(3, 2) },
> };
I would rename it meson_pwrc... since it's the same on all SoCs.
>
> +static struct meson_ee_pwrc_mem_domain meson8_pwrc_audio_dsp_mem[] = {
> + { HHI_MEM_PD_REG0, GENMASK(1, 0) },
> +};
> +
> +static struct meson_ee_pwrc_mem_domain meson8_pwrc_mem_vpu[] = {
> + VPU_MEMPD(HHI_VPU_MEM_PD_REG0),
> + VPU_MEMPD(HHI_VPU_MEM_PD_REG1),
> + VPU_HHI_MEMPD(HHI_MEM_PD_REG0),
> +};
> +
> static struct meson_ee_pwrc_mem_domain sm1_pwrc_mem_vpu[] = {
> VPU_MEMPD(HHI_VPU_MEM_PD_REG0),
> VPU_MEMPD(HHI_VPU_MEM_PD_REG1),
> @@ -201,7 +225,27 @@ static bool pwrc_ee_get_power(struct meson_ee_pwrc_domain *pwrc_domain);
> static struct meson_ee_pwrc_domain_desc g12a_pwrc_domains[] = {
> [PWRC_G12A_VPU_ID] = VPU_PD("VPU", &g12a_pwrc_vpu, g12a_pwrc_mem_vpu,
> pwrc_ee_get_power, 11, 2),
> - [PWRC_G12A_ETH_ID] = MEM_PD("ETH", g12a_pwrc_mem_eth),
> + [PWRC_G12A_ETH_ID] = MEM_PD("ETH", meson8_pwrc_mem_eth),
> +};
> +
> +static struct meson_ee_pwrc_domain_desc meson8_pwrc_domains[] = {
> + [PWRC_MESON8_VPU_ID] = VPU_PD("VPU", &meson8_pwrc_vpu,
> + meson8_pwrc_mem_vpu, pwrc_ee_get_power,
> + 0, 1),
> + [PWRC_MESON8_ETHERNET_MEM_ID] = MEM_PD("ETHERNET_MEM",
> + meson8_pwrc_mem_eth),
> + [PWRC_MESON8_AUDIO_DSP_MEM_ID] = MEM_PD("AUDIO_DSP_MEM",
> + meson8_pwrc_audio_dsp_mem),
> +};
> +
> +static struct meson_ee_pwrc_domain_desc meson8b_pwrc_domains[] = {
> + [PWRC_MESON8_VPU_ID] = VPU_PD("VPU", &meson8_pwrc_vpu,
> + meson8_pwrc_mem_vpu, pwrc_ee_get_power,
> + 11, 1),
> + [PWRC_MESON8_ETHERNET_MEM_ID] = MEM_PD("ETHERNET_MEM",
> + meson8_pwrc_mem_eth),
> + [PWRC_MESON8_AUDIO_DSP_MEM_ID] = MEM_PD("AUDIO_DSP_MEM",
> + meson8_pwrc_audio_dsp_mem),
> };
>
> static struct meson_ee_pwrc_domain_desc sm1_pwrc_domains[] = {
> @@ -216,7 +260,7 @@ static struct meson_ee_pwrc_domain_desc sm1_pwrc_domains[] = {
> [PWRC_SM1_GE2D_ID] = TOP_PD("GE2D", &sm1_pwrc_ge2d, sm1_pwrc_mem_ge2d,
> pwrc_ee_get_power),
> [PWRC_SM1_AUDIO_ID] = MEM_PD("AUDIO", sm1_pwrc_mem_audio),
> - [PWRC_SM1_ETH_ID] = MEM_PD("ETH", g12a_pwrc_mem_eth),
> + [PWRC_SM1_ETH_ID] = MEM_PD("ETH", meson8_pwrc_mem_eth),
> };
>
> struct meson_ee_pwrc_domain {
> @@ -470,12 +514,34 @@ static struct meson_ee_pwrc_domain_data meson_ee_g12a_pwrc_data = {
> .domains = g12a_pwrc_domains,
> };
>
> +static struct meson_ee_pwrc_domain_data meson_ee_m8_pwrc_data = {
> + .count = ARRAY_SIZE(meson8_pwrc_domains),
> + .domains = meson8_pwrc_domains,
> +};
> +
> +static struct meson_ee_pwrc_domain_data meson_ee_m8b_pwrc_data = {
> + .count = ARRAY_SIZE(meson8b_pwrc_domains),
> + .domains = meson8b_pwrc_domains,
> +};
> +
> static struct meson_ee_pwrc_domain_data meson_ee_sm1_pwrc_data = {
> .count = ARRAY_SIZE(sm1_pwrc_domains),
> .domains = sm1_pwrc_domains,
> };
>
> static const struct of_device_id meson_ee_pwrc_match_table[] = {
> + {
> + .compatible = "amlogic,meson8-pwrc",
> + .data = &meson_ee_m8_pwrc_data,
> + },
> + {
> + .compatible = "amlogic,meson8b-pwrc",
> + .data = &meson_ee_m8b_pwrc_data,
> + },
> + {
> + .compatible = "amlogic,meson8m2-pwrc",
> + .data = &meson_ee_m8b_pwrc_data,
> + },
> {
> .compatible = "amlogic,meson-g12a-pwrc",
> .data = &meson_ee_g12a_pwrc_data,
>
With these fixed:
Reviewed-by: Neil Armstrong <narmstrong@baylibre.com>
^ permalink raw reply
* [PATCH 3/3] arm64: dts: sparx5: Add SGPIO devices
From: Lars Povlsen @ 2020-05-13 14:11 UTC (permalink / raw)
To: SoC Team, Linus Walleij
Cc: Lars Povlsen, Microchip Linux Driver Support, devicetree,
linux-gpio, linux-arm-kernel, linux-kernel, Alexandre Belloni
In-Reply-To: <20200513141134.25819-1-lars.povlsen@microchip.com>
This adds SGPIO devices for the Sparx5 SoC and configures it for the
applicable reference boards.
Reviewed-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
Signed-off-by: Lars Povlsen <lars.povlsen@microchip.com>
---
arch/arm64/boot/dts/microchip/sparx5.dtsi | 52 +++++++++++++++++++
.../boot/dts/microchip/sparx5_pcb125.dts | 5 ++
.../dts/microchip/sparx5_pcb134_board.dtsi | 5 ++
3 files changed, 62 insertions(+)
diff --git a/arch/arm64/boot/dts/microchip/sparx5.dtsi b/arch/arm64/boot/dts/microchip/sparx5.dtsi
index 60629861a5157..b4fda5616536c 100644
--- a/arch/arm64/boot/dts/microchip/sparx5.dtsi
+++ b/arch/arm64/boot/dts/microchip/sparx5.dtsi
@@ -213,6 +213,22 @@ si2_pins: si2-pins {
function = "si2";
};
+ sgpio0_pins: sgpio-pins {
+ pins = "GPIO_0", "GPIO_1", "GPIO_2", "GPIO_3";
+ function = "sg0";
+ };
+
+ sgpio1_pins: sgpio1-pins {
+ pins = "GPIO_4", "GPIO_5", "GPIO_12", "GPIO_13";
+ function = "sg1";
+ };
+
+ sgpio2_pins: sgpio2-pins {
+ pins = "GPIO_30", "GPIO_31", "GPIO_32",
+ "GPIO_33";
+ function = "sg2";
+ };
+
uart_pins: uart-pins {
pins = "GPIO_10", "GPIO_11";
function = "uart";
@@ -243,6 +259,42 @@ emmc_pins: emmc-pins {
};
};
+ sgpio0: gpio@61101036c {
+ compatible = "mscc,ocelot-sgpio";
+ status = "disabled";
+ clocks = <&sys_clk>;
+ pinctrl-0 = <&sgpio0_pins>;
+ pinctrl-names = "default";
+ reg = <0x6 0x1101036c 0x100>;
+ gpio-controller;
+ #gpio-cells = <2>;
+ gpio-ranges = <&gpio 0 64 64>;
+ };
+
+ sgpio1: gpio@611010484 {
+ compatible = "mscc,ocelot-sgpio";
+ status = "disabled";
+ clocks = <&sys_clk>;
+ pinctrl-0 = <&sgpio1_pins>;
+ pinctrl-names = "default";
+ reg = <0x6 0x11010484 0x100>;
+ gpio-controller;
+ #gpio-cells = <2>;
+ gpio-ranges = <&gpio 0 128 64>;
+ };
+
+ sgpio2: gpio@61101059c {
+ compatible = "mscc,ocelot-sgpio";
+ status = "disabled";
+ clocks = <&sys_clk>;
+ pinctrl-0 = <&sgpio2_pins>;
+ pinctrl-names = "default";
+ reg = <0x6 0x1101059c 0x100>;
+ gpio-controller;
+ #gpio-cells = <2>;
+ gpio-ranges = <&gpio 0 192 64>;
+ };
+
i2c0: i2c@600101000 {
compatible = "snps,designware-i2c";
status = "disabled";
diff --git a/arch/arm64/boot/dts/microchip/sparx5_pcb125.dts b/arch/arm64/boot/dts/microchip/sparx5_pcb125.dts
index 94c4c3fd5a786..fd4f5b3ddcc49 100644
--- a/arch/arm64/boot/dts/microchip/sparx5_pcb125.dts
+++ b/arch/arm64/boot/dts/microchip/sparx5_pcb125.dts
@@ -55,6 +55,11 @@ spi-flash@1 {
};
};
+&sgpio0 {
+ status = "okay";
+ microchip,sgpio-ports = <0x00FFFFFF>;
+};
+
&i2c1 {
status = "okay";
};
diff --git a/arch/arm64/boot/dts/microchip/sparx5_pcb134_board.dtsi b/arch/arm64/boot/dts/microchip/sparx5_pcb134_board.dtsi
index 628a05d3f57ce..2f781258f8c99 100644
--- a/arch/arm64/boot/dts/microchip/sparx5_pcb134_board.dtsi
+++ b/arch/arm64/boot/dts/microchip/sparx5_pcb134_board.dtsi
@@ -47,6 +47,11 @@ spi-flash@0 {
};
};
+&sgpio0 {
+ status = "okay";
+ microchip,sgpio-ports = <0x00FFFFFF>;
+};
+
&gpio {
i2cmux_pins_i: i2cmux-pins-i {
pins = "GPIO_16", "GPIO_17", "GPIO_18", "GPIO_19",
--
2.26.2
^ permalink raw reply related
* [PATCH 2/3] pinctrl: mchp-sgpio: Add pinctrl driver for Microsemi Serial GPIO
From: Lars Povlsen @ 2020-05-13 14:11 UTC (permalink / raw)
To: SoC Team, Linus Walleij
Cc: Lars Povlsen, Microchip Linux Driver Support, devicetree,
linux-gpio, linux-arm-kernel, linux-kernel, Alexandre Belloni
In-Reply-To: <20200513141134.25819-1-lars.povlsen@microchip.com>
This adds a pinctrl driver for the Microsemi/Microchip Serial GPIO
(SGPIO) device used in various SoC's.
Reviewed-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
Signed-off-by: Lars Povlsen <lars.povlsen@microchip.com>
---
MAINTAINERS | 1 +
drivers/pinctrl/Kconfig | 17 +
drivers/pinctrl/Makefile | 1 +
drivers/pinctrl/pinctrl-mchp-sgpio.c | 569 +++++++++++++++++++++++++++
4 files changed, 588 insertions(+)
create mode 100644 drivers/pinctrl/pinctrl-mchp-sgpio.c
diff --git a/MAINTAINERS b/MAINTAINERS
index cdb63ca04670d..e5a7c41cdb6cd 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11237,6 +11237,7 @@ F: Documentation/devicetree/bindings/pinctrl/mscc,ocelot-sgpio.yaml
F: arch/mips/boot/dts/mscc/
F: arch/mips/configs/generic/board-ocelot.config
F: arch/mips/generic/board-ocelot.c
+F: drivers/pinctrl/pinctrl-mchp-sgpio.c
MICROSEMI SMART ARRAY SMARTPQI DRIVER (smartpqi)
M: Don Brace <don.brace@microsemi.com>
diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig
index 834c59950d1cf..2b0e9021fd7e0 100644
--- a/drivers/pinctrl/Kconfig
+++ b/drivers/pinctrl/Kconfig
@@ -396,6 +396,23 @@ config PINCTRL_OCELOT
select OF_GPIO
select REGMAP_MMIO
+config PINCTRL_MSCC_SGPIO
+ bool "Pinctrl driver for Microsemi Serial GPIO"
+ depends on OF
+ depends on HAS_IOMEM
+ select GPIOLIB
+ select GENERIC_PINCONF
+ select GENERIC_PINCTRL_GROUPS
+ select GENERIC_PINMUX_FUNCTIONS
+ select OF_GPIO
+ help
+ Support for the VCoreIII SoC serial GPIO device. By using a
+ serial interface, the SIO controller significantly extends
+ the number of available GPIOs with a minimum number of
+ additional pins on the device. The primary purpose of the
+ SIO controller is to connect control signals from SFP
+ modules and to act as an LED controller.
+
source "drivers/pinctrl/actions/Kconfig"
source "drivers/pinctrl/aspeed/Kconfig"
source "drivers/pinctrl/bcm/Kconfig"
diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile
index 0b36a1cfca8a1..1146ecc373edf 100644
--- a/drivers/pinctrl/Makefile
+++ b/drivers/pinctrl/Makefile
@@ -47,6 +47,7 @@ obj-$(CONFIG_PINCTRL_ZYNQ) += pinctrl-zynq.o
obj-$(CONFIG_PINCTRL_INGENIC) += pinctrl-ingenic.o
obj-$(CONFIG_PINCTRL_RK805) += pinctrl-rk805.o
obj-$(CONFIG_PINCTRL_OCELOT) += pinctrl-ocelot.o
+obj-$(CONFIG_PINCTRL_MSCC_SGPIO) += pinctrl-mchp-sgpio.o
obj-$(CONFIG_PINCTRL_EQUILIBRIUM) += pinctrl-equilibrium.o
obj-y += actions/
diff --git a/drivers/pinctrl/pinctrl-mchp-sgpio.c b/drivers/pinctrl/pinctrl-mchp-sgpio.c
new file mode 100644
index 0000000000000..e609f11137f48
--- /dev/null
+++ b/drivers/pinctrl/pinctrl-mchp-sgpio.c
@@ -0,0 +1,569 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+/*
+ * Microsemi SoCs serial gpio driver
+ *
+ * Author: <lars.povlsen@microchip.com>
+ *
+ * Copyright (c) 2020 Microchip Technology Inc. and its subsidiaries.
+ */
+
+#include <linux/gpio/driver.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/of_device.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/clk.h>
+#include <linux/pinctrl/pinctrl.h>
+#include <linux/pinctrl/pinmux.h>
+#include <linux/pinctrl/pinconf.h>
+#include <linux/pinctrl/pinconf-generic.h>
+#include <linux/platform_device.h>
+
+#include <dt-bindings/gpio/mchp-sgpio.h>
+
+#include "core.h"
+#include "pinconf.h"
+#include "pinmux.h"
+
+#define PIN_NAME_LEN (sizeof("SGPIO_pXXbY")+1)
+
+enum {
+ REG_INPUT_DATA,
+ REG_PORT_CONFIG,
+ REG_PORT_ENABLE,
+ REG_SIO_CONFIG,
+ REG_SIO_CLOCK,
+ MAXREG
+};
+
+struct mchp_sgpio_bf {
+ u8 beg;
+ u8 end;
+};
+
+struct mchp_sgpio_props {
+ const char *label;
+ u8 regoff[MAXREG];
+ struct mchp_sgpio_bf auto_repeat;
+ struct mchp_sgpio_bf port_width;
+ struct mchp_sgpio_bf clk_freq;
+ struct mchp_sgpio_bf bit_source;
+};
+
+#define __M(bf) GENMASK((bf).end, (bf).beg)
+#define __F(bf, x) (__M(bf) & ((x) << (bf).beg))
+#define __X(bf, x) (((x) >> (bf).beg) & GENMASK(((bf).end - (bf).beg), 0))
+
+#define MSCC_M_CFG_SIO_AUTO_REPEAT(p) BIT(p->props->auto_repeat.beg)
+#define MSCC_F_CFG_SIO_PORT_WIDTH(p, x) __F(p->props->port_width, x)
+#define MSCC_M_CFG_SIO_PORT_WIDTH(p) __M(p->props->port_width)
+#define MSCC_F_CLOCK_SIO_CLK_FREQ(p, x) __F(p->props->clk_freq, x)
+#define MSCC_M_CLOCK_SIO_CLK_FREQ(p) __M(p->props->clk_freq)
+#define MSCC_F_PORT_CFG_BIT_SOURCE(p, x) __F(p->props->bit_source, x)
+#define MSCC_X_PORT_CFG_BIT_SOURCE(p, x) __X(p->props->bit_source, x)
+
+const struct mchp_sgpio_props props_luton = {
+ .label = "luton-sgpio",
+ .regoff = { 0x00, 0x09, 0x29, 0x2a, 0x2b },
+ .auto_repeat = { 5, 5 },
+ .port_width = { 2, 3 },
+ .clk_freq = { 0, 11 },
+ .bit_source = { 0, 11 },
+};
+
+const struct mchp_sgpio_props props_ocelot = {
+ .label = "ocelot-sgpio",
+ .regoff = { 0x00, 0x06, 0x26, 0x04, 0x05 },
+ .auto_repeat = { 10, 10 },
+ .port_width = { 7, 8 },
+ .clk_freq = { 8, 19 },
+ .bit_source = { 12, 23 },
+};
+
+static const char * const functions[] = { "gpio" };
+
+struct mchp_sgpio_priv {
+ struct device *dev;
+ struct pinctrl_dev *pctl;
+ struct gpio_chip gpio;
+ u32 bitcount;
+ u32 ports;
+ u32 clock;
+ u32 mode[MSCC_SGPIOS_PER_BANK];
+ u32 __iomem *regs;
+ struct pinctrl_desc *desc;
+ const struct mchp_sgpio_props *props;
+};
+
+static inline u32 sgpio_readl(struct mchp_sgpio_priv *priv, u32 rno, u32 off)
+{
+ u32 __iomem *reg = &priv->regs[priv->props->regoff[rno] + off];
+
+ return readl(reg);
+}
+
+static inline void sgpio_writel(struct mchp_sgpio_priv *priv,
+ u32 val, u32 rno, u32 off)
+{
+ u32 __iomem *reg = &priv->regs[priv->props->regoff[rno] + off];
+
+ writel(val, reg);
+}
+
+static void sgpio_clrsetbits(struct mchp_sgpio_priv *priv,
+ u32 rno, u32 off, u32 clear, u32 set)
+{
+ u32 __iomem *reg = &priv->regs[priv->props->regoff[rno] + off];
+ u32 val = readl(reg);
+
+ val &= ~clear;
+ val |= set;
+
+ writel(val, reg);
+}
+
+static void sgpio_output_set(struct mchp_sgpio_priv *priv,
+ int pin, int value)
+{
+ u32 port = pin % MSCC_SGPIOS_PER_BANK;
+ u32 bit = pin / MSCC_SGPIOS_PER_BANK;
+ u32 mask = 3 << (3 * bit);
+
+ dev_dbg(priv->dev, "%s: port %d, bit %d, value %d\n",
+ __func__, port, bit, value);
+
+ value = (value & 3) << (3 * bit);
+ sgpio_clrsetbits(priv, REG_PORT_CONFIG, port,
+ MSCC_F_PORT_CFG_BIT_SOURCE(priv, mask),
+ MSCC_F_PORT_CFG_BIT_SOURCE(priv, value));
+}
+
+static int sgpio_output_get(struct mchp_sgpio_priv *priv, int pin)
+{
+ u32 port = pin % MSCC_SGPIOS_PER_BANK;
+ u32 bit = pin / MSCC_SGPIOS_PER_BANK;
+ u32 portval = sgpio_readl(priv, REG_PORT_CONFIG, port);
+ int ret;
+
+ ret = MSCC_X_PORT_CFG_BIT_SOURCE(priv, portval);
+ ret = !!(ret & (3 << (3 * bit)));
+
+ dev_dbg(priv->dev, "%s: port %d, bit %d, value %d\n",
+ __func__, port, bit, ret);
+
+ return ret;
+}
+
+static int sgpio_input_get(struct mchp_sgpio_priv *priv, int pin)
+{
+ u32 port = pin % MSCC_SGPIOS_PER_BANK;
+ u32 bit = pin / MSCC_SGPIOS_PER_BANK;
+ int ret;
+
+ ret = !!(sgpio_readl(priv, REG_INPUT_DATA, bit) & BIT(port));
+
+ dev_dbg(priv->dev, "%s: port %d, bit %d, value %d\n",
+ __func__, port, bit, ret);
+
+ return ret;
+}
+
+static int sgpio_pinconf_get(struct pinctrl_dev *pctldev,
+ unsigned int pin, unsigned long *config)
+{
+ struct mchp_sgpio_priv *priv = pinctrl_dev_get_drvdata(pctldev);
+ u32 param = pinconf_to_config_param(*config);
+ int val;
+
+ switch (param) {
+ case PIN_CONFIG_INPUT_ENABLE:
+ val = false;
+ break;
+
+ case PIN_CONFIG_OUTPUT_ENABLE:
+ val = true;
+ break;
+
+ case PIN_CONFIG_OUTPUT:
+ val = sgpio_output_get(priv, pin);
+ break;
+
+ default:
+ return -ENOTSUPP;
+ }
+
+ *config = pinconf_to_config_packed(param, val);
+
+ return 0;
+}
+
+int sgpio_pinconf_set(struct pinctrl_dev *pctldev, unsigned int pin,
+ unsigned long *configs, unsigned int num_configs)
+{
+ struct mchp_sgpio_priv *priv = pinctrl_dev_get_drvdata(pctldev);
+ u32 param, arg;
+ int cfg, err = 0;
+
+ for (cfg = 0; cfg < num_configs; cfg++) {
+ param = pinconf_to_config_param(configs[cfg]);
+ arg = pinconf_to_config_argument(configs[cfg]);
+
+ switch (param) {
+ case PIN_CONFIG_OUTPUT:
+ sgpio_output_set(priv, pin, arg);
+ break;
+
+ default:
+ err = -ENOTSUPP;
+ }
+ }
+
+ return err;
+}
+
+static const struct pinconf_ops sgpio_confops = {
+ .is_generic = true,
+ .pin_config_get = sgpio_pinconf_get,
+ .pin_config_set = sgpio_pinconf_set,
+ .pin_config_config_dbg_show = pinconf_generic_dump_config,
+};
+
+static int sgpio_get_functions_count(struct pinctrl_dev *pctldev)
+{
+ struct mchp_sgpio_priv *priv = pinctrl_dev_get_drvdata(pctldev);
+
+ dev_dbg(priv->dev, "%s\n", __func__);
+ return 1;
+}
+
+static const char *sgpio_get_function_name(struct pinctrl_dev *pctldev,
+ unsigned int function)
+{
+ struct mchp_sgpio_priv *priv = pinctrl_dev_get_drvdata(pctldev);
+
+ dev_dbg(priv->dev, "%s\n", __func__);
+
+ return functions[0];
+}
+
+static int sgpio_get_function_groups(struct pinctrl_dev *pctldev,
+ unsigned int function,
+ const char *const **groups,
+ unsigned *const num_groups)
+{
+ struct mchp_sgpio_priv *priv = pinctrl_dev_get_drvdata(pctldev);
+
+ dev_dbg(priv->dev, "%s\n", __func__);
+
+ *groups = functions;
+ *num_groups = ARRAY_SIZE(functions);
+
+ return 0;
+}
+
+static int sgpio_pinmux_set_mux(struct pinctrl_dev *pctldev,
+ unsigned int selector, unsigned int group)
+{
+ struct mchp_sgpio_priv *priv = pinctrl_dev_get_drvdata(pctldev);
+
+ dev_dbg(priv->dev, "%s: sel %d grp %d\n", __func__, selector, group);
+
+ return 0;
+}
+
+static int sgpio_gpio_set_direction(struct pinctrl_dev *pctldev,
+ struct pinctrl_gpio_range *range,
+ unsigned int pin, bool input)
+{
+ struct mchp_sgpio_priv *priv = pinctrl_dev_get_drvdata(pctldev);
+
+ dev_dbg(priv->dev, "%s: pin %d input %d\n", __func__, pin, input);
+
+ return 0;
+}
+
+static int sgpio_gpio_request_enable(struct pinctrl_dev *pctldev,
+ struct pinctrl_gpio_range *range,
+ unsigned int offset)
+{
+ struct mchp_sgpio_priv *priv = pinctrl_dev_get_drvdata(pctldev);
+
+ if ((priv->ports & BIT(offset)) == 0) {
+ dev_warn(priv->dev, "%s: Request port pin %d is not activated\n",
+ __func__, offset);
+ }
+
+ return 0;
+}
+
+static const struct pinmux_ops sgpio_pmx_ops = {
+ .get_functions_count = sgpio_get_functions_count,
+ .get_function_name = sgpio_get_function_name,
+ .get_function_groups = sgpio_get_function_groups,
+ .set_mux = sgpio_pinmux_set_mux,
+ .gpio_set_direction = sgpio_gpio_set_direction,
+ .gpio_request_enable = sgpio_gpio_request_enable,
+};
+
+static int sgpio_pctl_get_groups_count(struct pinctrl_dev *pctldev)
+{
+ struct mchp_sgpio_priv *priv = pinctrl_dev_get_drvdata(pctldev);
+
+ return priv->desc->npins;
+}
+
+static const char *sgpio_pctl_get_group_name(struct pinctrl_dev *pctldev,
+ unsigned int group)
+{
+ struct mchp_sgpio_priv *priv = pinctrl_dev_get_drvdata(pctldev);
+
+ return priv->desc->pins[group].name;
+}
+
+static int sgpio_pctl_get_group_pins(struct pinctrl_dev *pctldev,
+ unsigned int group,
+ const unsigned int **pins,
+ unsigned int *num_pins)
+{
+ struct mchp_sgpio_priv *priv = pinctrl_dev_get_drvdata(pctldev);
+
+ *pins = &priv->desc->pins[group].number;
+ *num_pins = 1;
+
+ return 0;
+}
+
+static const struct pinctrl_ops sgpio_pctl_ops = {
+ .get_groups_count = sgpio_pctl_get_groups_count,
+ .get_group_name = sgpio_pctl_get_group_name,
+ .get_group_pins = sgpio_pctl_get_group_pins,
+ .dt_node_to_map = pinconf_generic_dt_node_to_map_pin,
+ .dt_free_map = pinconf_generic_dt_free_map,
+};
+
+static struct pinctrl_desc sgpio_desc = {
+ .name = "sgpio-pinctrl",
+ .pctlops = &sgpio_pctl_ops,
+ .pmxops = &sgpio_pmx_ops,
+ .confops = &sgpio_confops,
+ .owner = THIS_MODULE,
+};
+
+static int mchp_sgpio_direction_input(struct gpio_chip *gc, unsigned int gpio)
+{
+ struct mchp_sgpio_priv *priv = gpiochip_get_data(gc);
+
+ u32 port = gpio % MSCC_SGPIOS_PER_BANK;
+ u32 bit = gpio / MSCC_SGPIOS_PER_BANK;
+
+ /* Set mode => input mode */
+ priv->mode[port] |= BIT(bit);
+
+ return 0;
+}
+
+static int mchp_sgpio_direction_output(struct gpio_chip *gc,
+ unsigned int gpio, int value)
+{
+ struct mchp_sgpio_priv *priv = gpiochip_get_data(gc);
+ u32 port = gpio % MSCC_SGPIOS_PER_BANK;
+ u32 bit = gpio / MSCC_SGPIOS_PER_BANK;
+
+ sgpio_output_set(priv, gpio, value);
+
+ /* Clear mode => output mode */
+ priv->mode[port] &= ~BIT(bit);
+
+ return 0;
+}
+
+static int mchp_sgpio_get_function(struct gpio_chip *gc, unsigned int gpio)
+{
+ struct mchp_sgpio_priv *priv = gpiochip_get_data(gc);
+ u32 port = gpio % MSCC_SGPIOS_PER_BANK;
+ u32 bit = gpio / MSCC_SGPIOS_PER_BANK;
+ u32 val = priv->mode[port] & BIT(bit);
+
+ return !!val; /* 0=out, 1=in */
+}
+
+static void mchp_sgpio_set_value(struct gpio_chip *gc,
+ unsigned int gpio, int value)
+{
+ mchp_sgpio_direction_output(gc, gpio, value);
+}
+
+static int mchp_sgpio_get_value(struct gpio_chip *gc, unsigned int gpio)
+{
+ struct mchp_sgpio_priv *priv = gpiochip_get_data(gc);
+ const struct pinctrl_pin_desc *pin = &priv->desc->pins[gpio];
+ int ret;
+
+ if (mchp_sgpio_get_function(gc, gpio))
+ ret = sgpio_input_get(priv, gpio);
+ else
+ ret = sgpio_output_get(priv, gpio);
+
+ dev_dbg(priv->dev, "get: gpio %d (%s), value %d\n",
+ gpio, pin->name, ret);
+
+ return ret;
+}
+
+static int mchp_sgpio_get_count(struct device *dev)
+{
+ struct device_node *np = dev->of_node;
+ struct of_phandle_args pinspec;
+ int count, index, ret;
+
+ for (count = 0, index = 0;; index++) {
+ ret = of_parse_phandle_with_fixed_args(np, "gpio-ranges", 3,
+ index, &pinspec);
+ if (ret)
+ break;
+
+ dev_dbg(dev, "%s: Add %d gpios\n", __func__, pinspec.args[2]);
+ count += pinspec.args[2];
+ }
+ dev_dbg(dev, "%s: Have %d gpios\n", __func__, count);
+ return count;
+}
+
+static int mchp_sgpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct mchp_sgpio_priv *priv;
+ int div_clock = 0, ret, port;
+ u32 val, ngpios;
+ struct resource *regs;
+ struct clk *clk;
+ struct pinctrl_pin_desc *pins;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->dev = dev;
+
+ clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(clk)) {
+ dev_err(dev, "Failed to get clock\n");
+ return PTR_ERR(clk);
+ }
+ div_clock = clk_get_rate(clk);
+
+ regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ priv->regs = devm_ioremap_resource(dev, regs);
+ if (IS_ERR(priv->regs))
+ return PTR_ERR(priv->regs);
+ priv->props = of_device_get_match_data(dev);
+ if (device_property_read_u32(dev, "microchip,sgpio-ports",
+ &priv->ports))
+ priv->ports = 0xFFFFFFFF;
+ if (device_property_read_u32(dev, "microchip,sgpio-frequency",
+ &priv->clock))
+ priv->clock = 12500000;
+ if (priv->clock <= 0 || priv->clock > div_clock) {
+ dev_err(dev, "Invalid frequency %d\n", priv->clock);
+ return -EINVAL;
+ }
+ ngpios = mchp_sgpio_get_count(dev);
+ if (ngpios < 1 ||
+ ngpios > (MSCC_SGPIO_BANK_DEPTH * MSCC_SGPIOS_PER_BANK)) {
+ dev_err(dev, "Invalid gpio count %d\n", ngpios);
+ return -EINVAL;
+ }
+
+ pins = devm_kzalloc(dev, sizeof(*pins)*ngpios, GFP_KERNEL);
+ if (pins) {
+ int i;
+ char *p, *names;
+
+ names = devm_kzalloc(dev, PIN_NAME_LEN*ngpios, GFP_KERNEL);
+
+ if (!names)
+ return -ENOMEM;
+
+ sgpio_desc.npins = ngpios;
+ sgpio_desc.pins = pins;
+
+ for (p = names, i = 0; i < ngpios; i++, p += PIN_NAME_LEN) {
+ snprintf(p, PIN_NAME_LEN, "SGPIO_p%db%d",
+ i % MSCC_SGPIOS_PER_BANK,
+ i / MSCC_SGPIOS_PER_BANK);
+ pins[i].number = i;
+ pins[i].name = p;
+ }
+ } else
+ return -ENOMEM;
+
+ priv->desc = &sgpio_desc;
+ priv->pctl = devm_pinctrl_register(&pdev->dev, priv->desc, priv);
+ if (IS_ERR(priv->pctl)) {
+ dev_err(&pdev->dev, "Failed to register pinctrl\n");
+ return PTR_ERR(priv->pctl);
+ }
+
+ priv->gpio.label = priv->props->label;
+ priv->gpio.parent = dev;
+ priv->gpio.of_node = dev->of_node;
+ priv->gpio.owner = THIS_MODULE;
+ priv->gpio.get_direction = mchp_sgpio_get_function;
+ priv->gpio.direction_input = mchp_sgpio_direction_input;
+ priv->gpio.direction_output = mchp_sgpio_direction_output;
+ priv->gpio.get = mchp_sgpio_get_value,
+ priv->gpio.set = mchp_sgpio_set_value;
+ priv->gpio.request = gpiochip_generic_request;
+ priv->gpio.free = gpiochip_generic_free;
+ priv->gpio.base = -1;
+ priv->gpio.ngpio = ngpios;
+
+ priv->bitcount = DIV_ROUND_UP(ngpios, MSCC_SGPIOS_PER_BANK);
+ dev_dbg(dev, "probe: gpios = %d, bit-count = %d\n",
+ ngpios, priv->bitcount);
+
+ sgpio_clrsetbits(priv, REG_SIO_CONFIG, 0,
+ MSCC_M_CFG_SIO_PORT_WIDTH(priv),
+ MSCC_F_CFG_SIO_PORT_WIDTH(priv, priv->bitcount - 1) |
+ MSCC_M_CFG_SIO_AUTO_REPEAT(priv));
+ val = div_clock / priv->clock;
+ dev_dbg(dev, "probe: div-clock = %d KHz, freq = %d KHz, div = %d\n",
+ div_clock / 1000, priv->clock / 1000, val);
+ sgpio_clrsetbits(priv, REG_SIO_CLOCK, 0,
+ MSCC_M_CLOCK_SIO_CLK_FREQ(priv),
+ MSCC_F_CLOCK_SIO_CLK_FREQ(priv, val));
+
+ for (port = 0; port < MSCC_SGPIOS_PER_BANK; port++)
+ sgpio_writel(priv, 0, REG_PORT_CONFIG, port);
+ sgpio_writel(priv, priv->ports, REG_PORT_ENABLE, 0);
+
+ ret = devm_gpiochip_add_data(dev, &priv->gpio, priv);
+ if (ret == 0)
+ dev_info(dev, "Registered %d GPIOs\n", ngpios);
+ else
+ dev_err(dev, "Failed to register: ret %d\n", ret);
+ return ret;
+}
+
+static const struct of_device_id mchp_sgpio_gpio_of_match[] = {
+ {
+ .compatible = "mscc,luton-sgpio",
+ .data = &props_luton,
+ }, {
+ .compatible = "mscc,ocelot-sgpio",
+ .data = &props_ocelot,
+ }, {
+ /* sentinel */
+ }
+};
+
+static struct platform_driver mchp_sgpio_pinctrl_driver = {
+ .driver = {
+ .name = "pinctrl-mchp-sgpio",
+ .of_match_table = mchp_sgpio_gpio_of_match,
+ .suppress_bind_attrs = true,
+ },
+ .probe = mchp_sgpio_probe,
+};
+builtin_platform_driver(mchp_sgpio_pinctrl_driver);
--
2.26.2
^ permalink raw reply related
* [PATCH 1/3] dt-bindings: pinctrl: Add bindings for mscc,ocelot-sgpio
From: Lars Povlsen @ 2020-05-13 14:11 UTC (permalink / raw)
To: SoC Team, Linus Walleij, Rob Herring
Cc: Lars Povlsen, Microchip Linux Driver Support, devicetree,
linux-gpio, linux-arm-kernel, linux-kernel, Alexandre Belloni
In-Reply-To: <20200513141134.25819-1-lars.povlsen@microchip.com>
This adds DT bindings for the Microsemi SGPIO controller, bindings
mscc,ocelot-sgpio and mscc,luton-sgpio.
Reviewed-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
Signed-off-by: Lars Povlsen <lars.povlsen@microchip.com>
---
.../bindings/pinctrl/mscc,ocelot-sgpio.yaml | 66 +++++++++++++++++++
MAINTAINERS | 1 +
include/dt-bindings/gpio/mchp-sgpio.h | 21 ++++++
3 files changed, 88 insertions(+)
create mode 100644 Documentation/devicetree/bindings/pinctrl/mscc,ocelot-sgpio.yaml
create mode 100644 include/dt-bindings/gpio/mchp-sgpio.h
diff --git a/Documentation/devicetree/bindings/pinctrl/mscc,ocelot-sgpio.yaml b/Documentation/devicetree/bindings/pinctrl/mscc,ocelot-sgpio.yaml
new file mode 100644
index 0000000000000..a332a0f4582fa
--- /dev/null
+++ b/Documentation/devicetree/bindings/pinctrl/mscc,ocelot-sgpio.yaml
@@ -0,0 +1,66 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/pinctrl/mscc,ocelot-sgpio.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Microsemi Serial GPIO controller
+
+maintainers:
+ - Lars Povlsen <lars.povlsen@microchip.com>
+
+description: |
+ By using a serial interface, the SIO controller significantly extend
+ the number of available GPIOs with a minimum number of additional
+ pins on the device. The primary purpose of the SIO controllers is to
+ connect control signals from SFP modules and to act as an LED
+ controller.
+
+properties:
+ $nodename:
+ pattern: "gpio"
+
+ compatible:
+ enum:
+ - mscc,ocelot-sgpio
+ - mscc,luton-sgpio
+
+ clocks:
+ maxItems: 1
+
+ gpio-controller: true
+
+ '#gpio-cells':
+ description: Specifying the pin number and flags, as defined in
+ include/dt-bindings/gpio/gpio.h
+ const: 2
+
+ gpio-ranges:
+ maxItems: 1
+
+ microchip,sgpio-ports:
+ description: This is a 32-bit bitmask, configuring whether a
+ particular port in the controller is enabled or not. This allows
+ unused ports to be removed from the bitstream and reduce latency.
+ $ref: "/schemas/types.yaml#/definitions/uint32"
+
+required:
+ - compatible
+ - clocks
+ - gpio-controller
+ - '#gpio-cells'
+ - gpio-ranges
+
+examples:
+ - |
+ sgpio0: gpio@61101036c {
+ compatible = "mscc,ocelot-sgpio";
+ clocks = <&sys_clk>;
+ pinctrl-0 = <&sgpio0_pins>;
+ pinctrl-names = "default";
+ reg = <0x1101036c 0x100>;
+ gpio-controller;
+ #gpio-cells = <2>;
+ gpio-ranges = <&gpio 0 64 64>;
+ microchip,sgpio-ports = <0x00ffffff>;
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index de64fd4548697..cdb63ca04670d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11233,6 +11233,7 @@ S: Supported
F: Documentation/devicetree/bindings/mips/mscc.txt
F: Documentation/devicetree/bindings/power/reset/ocelot-reset.txt
F: Documentation/devicetree/bindings/spi/mscc,ocelot-spi.yaml
+F: Documentation/devicetree/bindings/pinctrl/mscc,ocelot-sgpio.yaml
F: arch/mips/boot/dts/mscc/
F: arch/mips/configs/generic/board-ocelot.config
F: arch/mips/generic/board-ocelot.c
diff --git a/include/dt-bindings/gpio/mchp-sgpio.h b/include/dt-bindings/gpio/mchp-sgpio.h
new file mode 100644
index 0000000000000..0736158563f0a
--- /dev/null
+++ b/include/dt-bindings/gpio/mchp-sgpio.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * This header provides constants for binding mscc,*-sgpio
+ *
+ * The first cell in the SGPIO specifier is the GPIO ID. The macros below
+ * provide machros for this.
+ *
+ * The second cell contains standard flag values specified in gpio.h.
+ */
+
+#ifndef _DT_BINDINGS_GPIO_MSCC_SGPIO_H
+#define _DT_BINDINGS_GPIO_MSCC_SGPIO_H
+
+#include <dt-bindings/gpio/gpio.h>
+
+#define MSCC_SGPIOS_PER_BANK 32
+#define MSCC_SGPIO_BANK_DEPTH 4
+
+#define MSCC_SGPIO(port, bit) ((bit * MSCC_SGPIOS_PER_BANK) + port)
+
+#endif
--
2.26.2
^ permalink raw reply related
* [PATCH 0/3] pinctrl: Adding support for Microchip serial GPIO controller
From: Lars Povlsen @ 2020-05-13 14:11 UTC (permalink / raw)
To: SoC Team, Linus Walleij
Cc: Lars Povlsen, Microchip Linux Driver Support, devicetree,
linux-gpio, linux-arm-kernel, linux-kernel
This is an add-on series to the main SoC Sparx5 series
(Message-ID: <20200513125532.24585-1-lars.povlsen@microchip.com>).
The series add support for the serial GPIO controller used by Sparx5,
as well as (MSCC) ocelot/jaguar2.
The GPIO controller only supports output mode currently.
It is expected that the DT patches are to be taken directly by the arm-soc
maintainers.
Lars Povlsen (3):
dt-bindings: pinctrl: Add bindings for mscc,ocelot-sgpio
pinctrl: mchp-sgpio: Add pinctrl driver for Microsemi Serial GPIO
arm64: dts: sparx5: Add SGPIO devices
.../bindings/pinctrl/mscc,ocelot-sgpio.yaml | 66 ++
MAINTAINERS | 2 +
arch/arm64/boot/dts/microchip/sparx5.dtsi | 52 ++
.../boot/dts/microchip/sparx5_pcb125.dts | 5 +
.../dts/microchip/sparx5_pcb134_board.dtsi | 5 +
drivers/pinctrl/Kconfig | 17 +
drivers/pinctrl/Makefile | 1 +
drivers/pinctrl/pinctrl-mchp-sgpio.c | 569 ++++++++++++++++++
include/dt-bindings/gpio/mchp-sgpio.h | 21 +
9 files changed, 738 insertions(+)
create mode 100644 Documentation/devicetree/bindings/pinctrl/mscc,ocelot-sgpio.yaml
create mode 100644 drivers/pinctrl/pinctrl-mchp-sgpio.c
create mode 100644 include/dt-bindings/gpio/mchp-sgpio.h
--
2.26.2
^ permalink raw reply
* Re: [RFC PATCH] dt-bindings: display: ti,tfp410.txt: convert to yaml
From: Laurent Pinchart @ 2020-05-13 14:08 UTC (permalink / raw)
To: Ricardo Cañuelo
Cc: Tomi Valkeinen, robh+dt, kernel, devicetree, dri-devel,
linux-arm-kernel, jason
In-Reply-To: <20200513110957.dgb3axle24pmqp3a@rcn-XPS-13-9360>
Hi Ricardo,
On Wed, May 13, 2020 at 01:09:57PM +0200, Ricardo Cañuelo wrote:
> On mié 06-05-2020 18:53:20, Laurent Pinchart wrote:
> > I didn't if I remember correctly, I just mapped it to the hardware
> > features. The hardware register indeed takes a value between 0 and 7,
> > and that is mapped to [-4,3] x t(STEP). I don't mind either option.
>
> I was taking a look at the ti-tfp410.c driver to see if it needs any
> changes to support the updated deskew property ranges [0-7], but I don't
> fully understand what this does (line 276):
>
> /* Get the setup and hold time from vendor-specific properties. */
> of_property_read_u32(dvi->dev->of_node, "ti,deskew", (u32 *)&deskew);
> if (deskew < -4 || deskew > 3)
> return -EINVAL;
>
> timings->setup_time_ps = min(0, 1200 - 350 * deskew);
> timings->hold_time_ps = min(0, 1300 + 350 * deskew);
>
> It looks like that the driver doesn't really apply the deskew settings
> to the device and that this has not been really tested, so it's probably
> not a big deal.
The driver doesn't apply any setting to the device :-) The ti,deskew
property is meant to report the deskew settings selected by the chip's
configuration pins, not to set a value to be programmed to the device.
> I guess what you wanted to do was to adjust the setup and hold times
> around 1200 and 1300 ps respectively in increments/decrements of 350 ps
> depending on the deskew value, as the datasheet describes. But this code
> would set timings->setup_time_ps to 0 regardless of the deskew value,
> and timings->hold_time_ps would be either 0 or a very big integer value
> if deskew is -4 (both setup_time_ps and hold_time_ps are u32).
>
> Am I missing something? Was this intentional?
Oops. That's embarassing... It should clearly be a max(), not a min().
And only for hold_time_ps is this required.
Would you like to send a patch, or should I do so ?
--
Regards,
Laurent Pinchart
^ permalink raw reply
* [PATCH v4 5/5] drm/sun4i: mixer: Call of_dma_configure if there's an IOMMU
From: Maxime Ripard @ 2020-05-13 14:07 UTC (permalink / raw)
To: Joerg Roedel, Chen-Yu Tsai, Maxime Ripard, Mark Rutland,
Rob Herring, Frank Rowand
Cc: devicetree, linux-arm-kernel, iommu, Maxime Ripard
In-Reply-To: <cover.b27dedd61e008ffcf55a028ccddda3bb4d21dfc8.1589378833.git-series.maxime@cerno.tech>
The main DRM device is actually a virtual device so it doesn't have the
iommus property, which is instead on the DMA masters, in this case the
mixers.
Add a call to of_dma_configure with the mixers DT node but on the DRM
virtual device to configure it in the same way than the mixers.
Signed-off-by: Maxime Ripard <maxime@cerno.tech>
---
drivers/gpu/drm/sun4i/sun8i_mixer.c | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/drivers/gpu/drm/sun4i/sun8i_mixer.c b/drivers/gpu/drm/sun4i/sun8i_mixer.c
index 56cc037fd312..cc4fb916318f 100644
--- a/drivers/gpu/drm/sun4i/sun8i_mixer.c
+++ b/drivers/gpu/drm/sun4i/sun8i_mixer.c
@@ -363,6 +363,19 @@ static int sun8i_mixer_bind(struct device *dev, struct device *master,
mixer->engine.ops = &sun8i_engine_ops;
mixer->engine.node = dev->of_node;
+ if (of_find_property(dev->of_node, "iommus", NULL)) {
+ /*
+ * This assume we have the same DMA constraints for
+ * all our the mixers in our pipeline. This sounds
+ * bad, but it has always been the case for us, and
+ * DRM doesn't do per-device allocation either, so we
+ * would need to fix DRM first...
+ */
+ ret = of_dma_configure(drm->dev, dev->of_node, true);
+ if (ret)
+ return ret;
+ }
+
/*
* While this function can fail, we shouldn't do anything
* if this happens. Some early DE2 DT entries don't provide
--
git-series 0.9.1
^ permalink raw reply related
* [PATCH v4 3/5] iommu: Add Allwinner H6 IOMMU driver
From: Maxime Ripard @ 2020-05-13 14:07 UTC (permalink / raw)
To: Joerg Roedel, Chen-Yu Tsai, Maxime Ripard, Mark Rutland,
Rob Herring, Frank Rowand
Cc: devicetree, linux-arm-kernel, iommu, Maxime Ripard
In-Reply-To: <cover.b27dedd61e008ffcf55a028ccddda3bb4d21dfc8.1589378833.git-series.maxime@cerno.tech>
The Allwinner H6 has introduced an IOMMU for a few DMA controllers, mostly
video related: the display engine, the video decoders / encoders, the
camera capture controller, etc.
The design is pretty simple compared to other IOMMUs found in SoCs: there's
a single instance, controlling all the masters, with a single address
space.
It also features a performance monitoring unit that allows to retrieve
various informations (per-master and global TLB accesses, hits and misses,
access latency, etc) that isn't supported at the moment.
Signed-off-by: Maxime Ripard <maxime@cerno.tech>
---
drivers/iommu/Kconfig | 9 +-
drivers/iommu/Makefile | 1 +-
drivers/iommu/sun50i-iommu.c | 1027 +++++++++++++++++++++++++++++++++++-
3 files changed, 1037 insertions(+)
create mode 100644 drivers/iommu/sun50i-iommu.c
diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig
index 2ab07ce17abb..aca76383f201 100644
--- a/drivers/iommu/Kconfig
+++ b/drivers/iommu/Kconfig
@@ -303,6 +303,15 @@ config ROCKCHIP_IOMMU
Say Y here if you are using a Rockchip SoC that includes an IOMMU
device.
+config SUN50I_IOMMU
+ bool "Allwinner H6 IOMMU Support"
+ depends on ARCH_SUNXI || COMPILE_TEST
+ select ARM_DMA_USE_IOMMU
+ select IOMMU_API
+ select IOMMU_DMA
+ help
+ Support for the IOMMU introduced in the Allwinner H6 SoCs.
+
config TEGRA_IOMMU_GART
bool "Tegra GART IOMMU Support"
depends on ARCH_TEGRA_2x_SOC
diff --git a/drivers/iommu/Makefile b/drivers/iommu/Makefile
index 9f33fdb3bb05..57cf4ba5e27c 100644
--- a/drivers/iommu/Makefile
+++ b/drivers/iommu/Makefile
@@ -29,6 +29,7 @@ obj-$(CONFIG_MTK_IOMMU_V1) += mtk_iommu_v1.o
obj-$(CONFIG_OMAP_IOMMU) += omap-iommu.o
obj-$(CONFIG_OMAP_IOMMU_DEBUG) += omap-iommu-debug.o
obj-$(CONFIG_ROCKCHIP_IOMMU) += rockchip-iommu.o
+obj-$(CONFIG_SUN50I_IOMMU) += sun50i-iommu.o
obj-$(CONFIG_TEGRA_IOMMU_GART) += tegra-gart.o
obj-$(CONFIG_TEGRA_IOMMU_SMMU) += tegra-smmu.o
obj-$(CONFIG_EXYNOS_IOMMU) += exynos-iommu.o
diff --git a/drivers/iommu/sun50i-iommu.c b/drivers/iommu/sun50i-iommu.c
new file mode 100644
index 000000000000..989d87aa4426
--- /dev/null
+++ b/drivers/iommu/sun50i-iommu.c
@@ -0,0 +1,1027 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+// Copyright (C) 2016-2018, Allwinner Technology CO., LTD.
+// Copyright (C) 2019-2020, Cerno
+
+#include <linux/bitfield.h>
+#include <linux/bug.h>
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/dma-direction.h>
+#include <linux/dma-iommu.h>
+#include <linux/dma-mapping.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/iommu.h>
+#include <linux/iopoll.h>
+#include <linux/ioport.h>
+#include <linux/log2.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+#include <linux/reset.h>
+#include <linux/sizes.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+
+#define IOMMU_RESET_REG 0x010
+#define IOMMU_ENABLE_REG 0x020
+#define IOMMU_ENABLE_ENABLE BIT(0)
+
+#define IOMMU_BYPASS_REG 0x030
+#define IOMMU_AUTO_GATING_REG 0x040
+#define IOMMU_AUTO_GATING_ENABLE BIT(0)
+
+#define IOMMU_WBUF_CTRL_REG 0x044
+#define IOMMU_OOO_CTRL_REG 0x048
+#define IOMMU_4KB_BDY_PRT_CTRL_REG 0x04c
+#define IOMMU_TTB_REG 0x050
+#define IOMMU_TLB_ENABLE_REG 0x060
+#define IOMMU_TLB_PREFETCH_REG 0x070
+#define IOMMU_TLB_PREFETCH_MASTER_ENABLE(m) BIT(m)
+
+#define IOMMU_TLB_FLUSH_REG 0x080
+#define IOMMU_TLB_FLUSH_PTW_CACHE BIT(17)
+#define IOMMU_TLB_FLUSH_MACRO_TLB BIT(16)
+#define IOMMU_TLB_FLUSH_MICRO_TLB(i) (BIT(i) & GENMASK(5, 0))
+
+#define IOMMU_TLB_IVLD_ADDR_REG 0x090
+#define IOMMU_TLB_IVLD_ADDR_MASK_REG 0x094
+#define IOMMU_TLB_IVLD_ENABLE_REG 0x098
+#define IOMMU_TLB_IVLD_ENABLE_ENABLE BIT(0)
+
+#define IOMMU_PC_IVLD_ADDR_REG 0x0a0
+#define IOMMU_PC_IVLD_ENABLE_REG 0x0a8
+#define IOMMU_PC_IVLD_ENABLE_ENABLE BIT(0)
+
+#define IOMMU_DM_AUT_CTRL_REG(d) (0x0b0 + ((d) / 2) * 4)
+#define IOMMU_DM_AUT_CTRL_RD_UNAVAIL(d, m) (1 << (((d & 1) * 16) + ((m) * 2)))
+#define IOMMU_DM_AUT_CTRL_WR_UNAVAIL(d, m) (1 << (((d & 1) * 16) + ((m) * 2) + 1))
+
+#define IOMMU_DM_AUT_OVWT_REG 0x0d0
+#define IOMMU_INT_ENABLE_REG 0x100
+#define IOMMU_INT_CLR_REG 0x104
+#define IOMMU_INT_STA_REG 0x108
+#define IOMMU_INT_ERR_ADDR_REG(i) (0x110 + (i) * 4)
+#define IOMMU_INT_ERR_ADDR_L1_REG 0x130
+#define IOMMU_INT_ERR_ADDR_L2_REG 0x134
+#define IOMMU_INT_ERR_DATA_REG(i) (0x150 + (i) * 4)
+#define IOMMU_L1PG_INT_REG 0x0180
+#define IOMMU_L2PG_INT_REG 0x0184
+
+#define IOMMU_INT_INVALID_L2PG BIT(17)
+#define IOMMU_INT_INVALID_L1PG BIT(16)
+#define IOMMU_INT_MASTER_PERMISSION(m) BIT(m)
+#define IOMMU_INT_MASTER_MASK (IOMMU_INT_MASTER_PERMISSION(0) | \
+ IOMMU_INT_MASTER_PERMISSION(1) | \
+ IOMMU_INT_MASTER_PERMISSION(2) | \
+ IOMMU_INT_MASTER_PERMISSION(3) | \
+ IOMMU_INT_MASTER_PERMISSION(4) | \
+ IOMMU_INT_MASTER_PERMISSION(5))
+#define IOMMU_INT_MASK (IOMMU_INT_INVALID_L1PG | \
+ IOMMU_INT_INVALID_L2PG | \
+ IOMMU_INT_MASTER_MASK)
+
+#define PT_ENTRY_SIZE sizeof(u32)
+
+#define NUM_DT_ENTRIES 4096
+#define DT_SIZE (NUM_DT_ENTRIES * PT_ENTRY_SIZE)
+
+#define NUM_PT_ENTRIES 256
+#define PT_SIZE (NUM_PT_ENTRIES * PT_ENTRY_SIZE)
+
+struct sun50i_iommu {
+ struct iommu_device iommu;
+
+ /* Lock to modify the IOMMU registers */
+ spinlock_t iommu_lock;
+
+ struct device *dev;
+ void __iomem *base;
+ struct reset_control *reset;
+ struct clk *clk;
+
+ struct iommu_domain *domain;
+ struct iommu_group *group;
+ struct kmem_cache *pt_pool;
+};
+
+struct sun50i_iommu_domain {
+ struct iommu_domain domain;
+
+ /* Number of devices attached to the domain */
+ refcount_t refcnt;
+
+ /* L1 Page Table */
+ u32 *dt;
+ dma_addr_t dt_dma;
+
+ struct sun50i_iommu *iommu;
+};
+
+static struct sun50i_iommu_domain *to_sun50i_domain(struct iommu_domain *domain)
+{
+ return container_of(domain, struct sun50i_iommu_domain, domain);
+}
+
+static struct sun50i_iommu *sun50i_iommu_from_dev(struct device *dev)
+{
+ return dev_iommu_priv_get(dev);
+}
+
+static u32 iommu_read(struct sun50i_iommu *iommu, u32 offset)
+{
+ return readl(iommu->base + offset);
+}
+
+static void iommu_write(struct sun50i_iommu *iommu, u32 offset, u32 value)
+{
+ writel(value, iommu->base + offset);
+}
+
+/*
+ * The Allwinner H6 IOMMU uses a 2-level page table.
+ *
+ * The first level is the usual Directory Table (DT), that consists of
+ * 4096 4-bytes Directory Table Entries (DTE), each pointing to a Page
+ * Table (PT).
+ *
+ * Each PT consits of 256 4-bytes Page Table Entries (PTE), each
+ * pointing to a 4kB page of physical memory.
+ *
+ * The IOMMU supports a single DT, pointed by the IOMMU_TTB_REG
+ * register that contains its physical address.
+ */
+
+#define SUN50I_IOVA_DTE_MASK GENMASK(31, 20)
+#define SUN50I_IOVA_PTE_MASK GENMASK(19, 12)
+#define SUN50I_IOVA_PAGE_MASK GENMASK(11, 0)
+
+static u32 sun50i_iova_get_dte_index(dma_addr_t iova)
+{
+ return FIELD_GET(SUN50I_IOVA_DTE_MASK, iova);
+}
+
+static u32 sun50i_iova_get_pte_index(dma_addr_t iova)
+{
+ return FIELD_GET(SUN50I_IOVA_PTE_MASK, iova);
+}
+
+static u32 sun50i_iova_get_page_offset(dma_addr_t iova)
+{
+ return FIELD_GET(SUN50I_IOVA_PAGE_MASK, iova);
+}
+
+/*
+ * Each Directory Table Entry has a Page Table address and a valid
+ * bit:
+
+ * +---------------------+-----------+-+
+ * | PT address | Reserved |V|
+ * +---------------------+-----------+-+
+ * 31:10 - Page Table address
+ * 9:2 - Reserved
+ * 1:0 - 1 if the entry is valid
+ */
+
+#define SUN50I_DTE_PT_ADDRESS_MASK GENMASK(31, 10)
+#define SUN50I_DTE_PT_ATTRS GENMASK(1, 0)
+#define SUN50I_DTE_PT_VALID 1
+
+static phys_addr_t sun50i_dte_get_pt_address(u32 dte)
+{
+ return (phys_addr_t)dte & SUN50I_DTE_PT_ADDRESS_MASK;
+}
+
+static bool sun50i_dte_is_pt_valid(u32 dte)
+{
+ return (dte & SUN50I_DTE_PT_ATTRS) == SUN50I_DTE_PT_VALID;
+}
+
+static u32 sun50i_mk_dte(dma_addr_t pt_dma)
+{
+ return (pt_dma & SUN50I_DTE_PT_ADDRESS_MASK) | SUN50I_DTE_PT_VALID;
+}
+
+/*
+ * Each PTE has a Page address, an authority index and a valid bit:
+ *
+ * +----------------+-----+-----+-----+---+-----+
+ * | Page address | Rsv | ACI | Rsv | V | Rsv |
+ * +----------------+-----+-----+-----+---+-----+
+ * 31:12 - Page address
+ * 11:8 - Reserved
+ * 7:4 - Authority Control Index
+ * 3:2 - Reserved
+ * 1 - 1 if the entry is valid
+ * 0 - Reserved
+ *
+ * The way permissions work is that the IOMMU has 16 "domains" that
+ * can be configured to give each masters either read or write
+ * permissions through the IOMMU_DM_AUT_CTRL_REG registers. The domain
+ * 0 seems like the default domain, and its permissions in the
+ * IOMMU_DM_AUT_CTRL_REG are only read-only, so it's not really
+ * useful to enforce any particular permission.
+ *
+ * Each page entry will then have a reference to the domain they are
+ * affected to, so that we can actually enforce them on a per-page
+ * basis.
+ *
+ * In order to make it work with the IOMMU framework, we will be using
+ * 4 different domains, starting at 1: RD_WR, RD, WR and NONE
+ * depending on the permission we want to enforce. Each domain will
+ * have each master setup in the same way, since the IOMMU framework
+ * doesn't seem to restrict page access on a per-device basis. And
+ * then we will use the relevant domain index when generating the page
+ * table entry depending on the permissions we want to be enforced.
+ */
+
+enum sun50i_iommu_aci {
+ SUN50I_IOMMU_ACI_DO_NOT_USE = 0,
+ SUN50I_IOMMU_ACI_NONE,
+ SUN50I_IOMMU_ACI_RD,
+ SUN50I_IOMMU_ACI_WR,
+ SUN50I_IOMMU_ACI_RD_WR,
+};
+
+#define SUN50I_PTE_PAGE_ADDRESS_MASK GENMASK(31, 12)
+#define SUN50I_PTE_ACI_MASK GENMASK(7, 4)
+#define SUN50I_PTE_PAGE_VALID BIT(1)
+
+static phys_addr_t sun50i_pte_get_page_address(u32 pte)
+{
+ return (phys_addr_t)pte & SUN50I_PTE_PAGE_ADDRESS_MASK;
+}
+
+static enum sun50i_iommu_aci sun50i_get_pte_aci(u32 pte)
+{
+ return FIELD_GET(SUN50I_PTE_ACI_MASK, pte);
+}
+
+static bool sun50i_pte_is_page_valid(u32 pte)
+{
+ return pte & SUN50I_PTE_PAGE_VALID;
+}
+
+static u32 sun50i_mk_pte(phys_addr_t page, int prot)
+{
+ enum sun50i_iommu_aci aci;
+ u32 flags = 0;
+
+ if (prot & (IOMMU_READ | IOMMU_WRITE))
+ aci = SUN50I_IOMMU_ACI_RD_WR;
+ else if (prot & IOMMU_READ)
+ aci = SUN50I_IOMMU_ACI_RD;
+ else if (prot & IOMMU_WRITE)
+ aci = SUN50I_IOMMU_ACI_WR;
+ else
+ aci = SUN50I_IOMMU_ACI_NONE;
+
+ flags |= FIELD_PREP(SUN50I_PTE_ACI_MASK, aci);
+ page &= SUN50I_PTE_PAGE_ADDRESS_MASK;
+ return page | flags | SUN50I_PTE_PAGE_VALID;
+}
+
+static void sun50i_table_flush(struct sun50i_iommu_domain *sun50i_domain,
+ void *vaddr, unsigned int count)
+{
+ struct sun50i_iommu *iommu = sun50i_domain->iommu;
+ dma_addr_t dma = virt_to_phys(vaddr);
+ size_t size = count * PT_ENTRY_SIZE;
+
+ dma_sync_single_for_device(iommu->dev, dma, size, DMA_TO_DEVICE);
+}
+
+static int sun50i_iommu_flush_all_tlb(struct sun50i_iommu *iommu)
+{
+ u32 reg;
+ int ret;
+
+ assert_spin_locked(&iommu->iommu_lock);
+
+ iommu_write(iommu,
+ IOMMU_TLB_FLUSH_REG,
+ IOMMU_TLB_FLUSH_PTW_CACHE |
+ IOMMU_TLB_FLUSH_MACRO_TLB |
+ IOMMU_TLB_FLUSH_MICRO_TLB(5) |
+ IOMMU_TLB_FLUSH_MICRO_TLB(4) |
+ IOMMU_TLB_FLUSH_MICRO_TLB(3) |
+ IOMMU_TLB_FLUSH_MICRO_TLB(2) |
+ IOMMU_TLB_FLUSH_MICRO_TLB(1) |
+ IOMMU_TLB_FLUSH_MICRO_TLB(0));
+
+ ret = readl_poll_timeout(iommu->base + IOMMU_TLB_FLUSH_REG,
+ reg, !reg,
+ 1, 2000);
+ if (ret)
+ dev_warn(iommu->dev, "TLB Flush timed out!\n");
+
+ return ret;
+}
+
+static void sun50i_iommu_flush_iotlb_all(struct iommu_domain *domain)
+{
+ struct sun50i_iommu_domain *sun50i_domain = to_sun50i_domain(domain);
+ struct sun50i_iommu *iommu = sun50i_domain->iommu;
+ unsigned long flags;
+
+ /*
+ * At boot, we'll have a first call into .flush_iotlb_all right after
+ * .probe_device, and since we link our (single) domain to our iommu in
+ * the .attach_device callback, we don't have that pointer set.
+ *
+ * It shouldn't really be any trouble to ignore it though since we flush
+ * all caches as part of the device powerup.
+ */
+ if (!iommu)
+ return;
+
+ spin_lock_irqsave(&iommu->iommu_lock, flags);
+ sun50i_iommu_flush_all_tlb(iommu);
+ spin_unlock_irqrestore(&iommu->iommu_lock, flags);
+}
+
+static void sun50i_iommu_iotlb_sync(struct iommu_domain *domain,
+ struct iommu_iotlb_gather *gather)
+{
+ sun50i_iommu_flush_iotlb_all(domain);
+}
+
+static int sun50i_iommu_enable(struct sun50i_iommu *iommu)
+{
+ struct sun50i_iommu_domain *sun50i_domain;
+ unsigned long flags;
+ int ret;
+
+ if (!iommu->domain)
+ return 0;
+
+ sun50i_domain = to_sun50i_domain(iommu->domain);
+
+ ret = reset_control_deassert(iommu->reset);
+ if (ret)
+ return ret;
+
+ ret = clk_prepare_enable(iommu->clk);
+ if (ret)
+ goto err_reset_assert;
+
+ spin_lock_irqsave(&iommu->iommu_lock, flags);
+
+ iommu_write(iommu, IOMMU_TTB_REG, sun50i_domain->dt_dma);
+ iommu_write(iommu, IOMMU_TLB_PREFETCH_REG,
+ IOMMU_TLB_PREFETCH_MASTER_ENABLE(0) |
+ IOMMU_TLB_PREFETCH_MASTER_ENABLE(1) |
+ IOMMU_TLB_PREFETCH_MASTER_ENABLE(2) |
+ IOMMU_TLB_PREFETCH_MASTER_ENABLE(3) |
+ IOMMU_TLB_PREFETCH_MASTER_ENABLE(4) |
+ IOMMU_TLB_PREFETCH_MASTER_ENABLE(5));
+ iommu_write(iommu, IOMMU_INT_ENABLE_REG, IOMMU_INT_MASK);
+ iommu_write(iommu, IOMMU_DM_AUT_CTRL_REG(SUN50I_IOMMU_ACI_NONE),
+ IOMMU_DM_AUT_CTRL_RD_UNAVAIL(SUN50I_IOMMU_ACI_NONE, 0) |
+ IOMMU_DM_AUT_CTRL_WR_UNAVAIL(SUN50I_IOMMU_ACI_NONE, 0) |
+ IOMMU_DM_AUT_CTRL_RD_UNAVAIL(SUN50I_IOMMU_ACI_NONE, 1) |
+ IOMMU_DM_AUT_CTRL_WR_UNAVAIL(SUN50I_IOMMU_ACI_NONE, 1) |
+ IOMMU_DM_AUT_CTRL_RD_UNAVAIL(SUN50I_IOMMU_ACI_NONE, 2) |
+ IOMMU_DM_AUT_CTRL_WR_UNAVAIL(SUN50I_IOMMU_ACI_NONE, 2) |
+ IOMMU_DM_AUT_CTRL_RD_UNAVAIL(SUN50I_IOMMU_ACI_NONE, 3) |
+ IOMMU_DM_AUT_CTRL_WR_UNAVAIL(SUN50I_IOMMU_ACI_NONE, 3) |
+ IOMMU_DM_AUT_CTRL_RD_UNAVAIL(SUN50I_IOMMU_ACI_NONE, 4) |
+ IOMMU_DM_AUT_CTRL_WR_UNAVAIL(SUN50I_IOMMU_ACI_NONE, 4) |
+ IOMMU_DM_AUT_CTRL_RD_UNAVAIL(SUN50I_IOMMU_ACI_NONE, 5) |
+ IOMMU_DM_AUT_CTRL_WR_UNAVAIL(SUN50I_IOMMU_ACI_NONE, 5));
+
+ iommu_write(iommu, IOMMU_DM_AUT_CTRL_REG(SUN50I_IOMMU_ACI_RD),
+ IOMMU_DM_AUT_CTRL_WR_UNAVAIL(SUN50I_IOMMU_ACI_RD, 0) |
+ IOMMU_DM_AUT_CTRL_WR_UNAVAIL(SUN50I_IOMMU_ACI_RD, 1) |
+ IOMMU_DM_AUT_CTRL_WR_UNAVAIL(SUN50I_IOMMU_ACI_RD, 2) |
+ IOMMU_DM_AUT_CTRL_WR_UNAVAIL(SUN50I_IOMMU_ACI_RD, 3) |
+ IOMMU_DM_AUT_CTRL_WR_UNAVAIL(SUN50I_IOMMU_ACI_RD, 4) |
+ IOMMU_DM_AUT_CTRL_WR_UNAVAIL(SUN50I_IOMMU_ACI_RD, 5));
+
+ iommu_write(iommu, IOMMU_DM_AUT_CTRL_REG(SUN50I_IOMMU_ACI_WR),
+ IOMMU_DM_AUT_CTRL_RD_UNAVAIL(SUN50I_IOMMU_ACI_WR, 0) |
+ IOMMU_DM_AUT_CTRL_RD_UNAVAIL(SUN50I_IOMMU_ACI_WR, 1) |
+ IOMMU_DM_AUT_CTRL_RD_UNAVAIL(SUN50I_IOMMU_ACI_WR, 2) |
+ IOMMU_DM_AUT_CTRL_RD_UNAVAIL(SUN50I_IOMMU_ACI_WR, 3) |
+ IOMMU_DM_AUT_CTRL_RD_UNAVAIL(SUN50I_IOMMU_ACI_WR, 4) |
+ IOMMU_DM_AUT_CTRL_RD_UNAVAIL(SUN50I_IOMMU_ACI_WR, 5));
+
+ ret = sun50i_iommu_flush_all_tlb(iommu);
+ if (ret) {
+ spin_unlock_irqrestore(&iommu->iommu_lock, flags);
+ goto err_clk_disable;
+ }
+
+ iommu_write(iommu, IOMMU_AUTO_GATING_REG, IOMMU_AUTO_GATING_ENABLE);
+ iommu_write(iommu, IOMMU_ENABLE_REG, IOMMU_ENABLE_ENABLE);
+
+ spin_unlock_irqrestore(&iommu->iommu_lock, flags);
+
+ return 0;
+
+err_clk_disable:
+ clk_disable_unprepare(iommu->clk);
+
+err_reset_assert:
+ reset_control_assert(iommu->reset);
+
+ return ret;
+}
+
+static void sun50i_iommu_disable(struct sun50i_iommu *iommu)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&iommu->iommu_lock, flags);
+
+ iommu_write(iommu, IOMMU_ENABLE_REG, 0);
+ iommu_write(iommu, IOMMU_TTB_REG, 0);
+
+ spin_unlock_irqrestore(&iommu->iommu_lock, flags);
+
+ clk_disable_unprepare(iommu->clk);
+ reset_control_assert(iommu->reset);
+}
+
+static void *sun50i_iommu_alloc_page_table(struct sun50i_iommu *iommu,
+ gfp_t gfp)
+{
+ dma_addr_t pt_dma;
+ u32 *page_table;
+
+ page_table = kmem_cache_zalloc(iommu->pt_pool, gfp);
+ if (!page_table)
+ return ERR_PTR(-ENOMEM);
+
+ pt_dma = dma_map_single(iommu->dev, page_table, PT_SIZE, DMA_TO_DEVICE);
+ if (dma_mapping_error(iommu->dev, pt_dma)) {
+ dev_err(iommu->dev, "Couldn't map L2 Page Table\n");
+ kmem_cache_free(iommu->pt_pool, page_table);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ /* We rely on the physical address and DMA address being the same */
+ WARN_ON(pt_dma != virt_to_phys(page_table));
+
+ return page_table;
+}
+
+static void sun50i_iommu_free_page_table(struct sun50i_iommu *iommu,
+ u32 *page_table)
+{
+ phys_addr_t pt_phys = virt_to_phys(page_table);
+
+ dma_unmap_single(iommu->dev, pt_phys, PT_SIZE, DMA_TO_DEVICE);
+ kmem_cache_free(iommu->pt_pool, page_table);
+}
+
+static u32 *sun50i_dte_get_page_table(struct sun50i_iommu_domain *sun50i_domain,
+ dma_addr_t iova, gfp_t gfp)
+{
+ struct sun50i_iommu *iommu = sun50i_domain->iommu;
+ unsigned long flags;
+ u32 *page_table;
+ u32 *dte_addr;
+ u32 old_dte;
+ u32 dte;
+
+ dte_addr = &sun50i_domain->dt[sun50i_iova_get_dte_index(iova)];
+ dte = *dte_addr;
+ if (sun50i_dte_is_pt_valid(dte)) {
+ phys_addr_t pt_phys = sun50i_dte_get_pt_address(dte);
+ return (u32 *)phys_to_virt(pt_phys);
+ }
+
+ page_table = sun50i_iommu_alloc_page_table(iommu, gfp);
+ if (IS_ERR(page_table))
+ return page_table;
+
+ dte = sun50i_mk_dte(virt_to_phys(page_table));
+ old_dte = cmpxchg(dte_addr, 0, dte);
+ if (old_dte) {
+ phys_addr_t installed_pt_phys =
+ sun50i_dte_get_pt_address(old_dte);
+ u32 *installed_pt = phys_to_virt(installed_pt_phys);
+ u32 *drop_pt = page_table;
+
+ page_table = installed_pt;
+ dte = old_dte;
+ sun50i_iommu_free_page_table(iommu, drop_pt);
+ }
+
+ sun50i_table_flush(sun50i_domain, page_table, PT_SIZE);
+ sun50i_table_flush(sun50i_domain, dte_addr, 1);
+
+ return page_table;
+}
+
+static int sun50i_iommu_map(struct iommu_domain *domain, unsigned long iova,
+ phys_addr_t paddr, size_t size, int prot, gfp_t gfp)
+{
+ struct sun50i_iommu_domain *sun50i_domain = to_sun50i_domain(domain);
+ struct sun50i_iommu *iommu = sun50i_domain->iommu;
+ u32 pte_index;
+ u32 *page_table, *pte_addr;
+ int ret = 0;
+
+ page_table = sun50i_dte_get_page_table(sun50i_domain, iova, gfp);
+ if (IS_ERR(page_table)) {
+ ret = PTR_ERR(page_table);
+ goto out;
+ }
+
+ pte_index = sun50i_iova_get_pte_index(iova);
+ pte_addr = &page_table[pte_index];
+ if (unlikely(sun50i_pte_is_page_valid(*pte_addr))) {
+ phys_addr_t page_phys = sun50i_pte_get_page_address(*pte_addr);
+ dev_err(iommu->dev,
+ "iova %pad already mapped to %pa cannot remap to %pa prot: %#x\n",
+ &iova, &page_phys, &paddr, prot);
+ ret = -EBUSY;
+ goto out;
+ }
+
+ *pte_addr = sun50i_mk_pte(paddr, prot);
+ sun50i_table_flush(sun50i_domain, pte_addr, 1);
+
+out:
+ return ret;
+}
+
+static size_t sun50i_iommu_unmap(struct iommu_domain *domain, unsigned long iova,
+ size_t size, struct iommu_iotlb_gather *gather)
+{
+ struct sun50i_iommu_domain *sun50i_domain = to_sun50i_domain(domain);
+ struct sun50i_iommu *iommu = sun50i_domain->iommu;
+ phys_addr_t pt_phys;
+ dma_addr_t pte_dma;
+ u32 *pte_addr;
+ u32 dte;
+
+ dte = sun50i_domain->dt[sun50i_iova_get_dte_index(iova)];
+ if (!sun50i_dte_is_pt_valid(dte))
+ return 0;
+
+ pt_phys = sun50i_dte_get_pt_address(dte);
+ pte_addr = (u32 *)phys_to_virt(pt_phys) + sun50i_iova_get_pte_index(iova);
+ pte_dma = pt_phys + sun50i_iova_get_pte_index(iova) * PT_ENTRY_SIZE;
+
+ if (!sun50i_pte_is_page_valid(*pte_addr))
+ return 0;
+
+ memset(pte_addr, 0, sizeof(*pte_addr));
+ sun50i_table_flush(sun50i_domain, pte_addr, 1);
+
+ return SZ_4K;
+}
+
+static phys_addr_t sun50i_iommu_iova_to_phys(struct iommu_domain *domain,
+ dma_addr_t iova)
+{
+ struct sun50i_iommu_domain *sun50i_domain = to_sun50i_domain(domain);
+ phys_addr_t pt_phys;
+ u32 *page_table;
+ u32 dte, pte;
+
+ dte = sun50i_domain->dt[sun50i_iova_get_dte_index(iova)];
+ if (!sun50i_dte_is_pt_valid(dte))
+ return 0;
+
+ pt_phys = sun50i_dte_get_pt_address(dte);
+ page_table = (u32 *)phys_to_virt(pt_phys);
+ pte = page_table[sun50i_iova_get_pte_index(iova)];
+ if (!sun50i_pte_is_page_valid(pte))
+ return 0;
+
+ return sun50i_pte_get_page_address(pte) +
+ sun50i_iova_get_page_offset(iova);
+}
+
+static struct iommu_domain *sun50i_iommu_domain_alloc(unsigned type)
+{
+ struct sun50i_iommu_domain *sun50i_domain;
+
+ if (type != IOMMU_DOMAIN_DMA &&
+ type != IOMMU_DOMAIN_IDENTITY &&
+ type != IOMMU_DOMAIN_UNMANAGED)
+ return NULL;
+
+ sun50i_domain = kzalloc(sizeof(*sun50i_domain), GFP_KERNEL);
+ if (!sun50i_domain)
+ return NULL;
+
+ if (type == IOMMU_DOMAIN_DMA &&
+ iommu_get_dma_cookie(&sun50i_domain->domain))
+ goto err_free_domain;
+
+ sun50i_domain->dt = (u32 *)__get_free_pages(GFP_KERNEL,
+ get_order(DT_SIZE));
+ if (!sun50i_domain->dt)
+ goto err_put_cookie;
+ memset(sun50i_domain->dt, 0, DT_SIZE);
+
+ refcount_set(&sun50i_domain->refcnt, 1);
+
+ sun50i_domain->domain.geometry.aperture_start = 0;
+ sun50i_domain->domain.geometry.aperture_end = DMA_BIT_MASK(32);
+ sun50i_domain->domain.geometry.force_aperture = true;
+
+ return &sun50i_domain->domain;
+
+err_put_cookie:
+ if (type == IOMMU_DOMAIN_DMA)
+ iommu_put_dma_cookie(&sun50i_domain->domain);
+
+err_free_domain:
+ kfree(sun50i_domain);
+
+ return NULL;
+}
+
+static void sun50i_iommu_domain_free(struct iommu_domain *domain)
+{
+ struct sun50i_iommu_domain *sun50i_domain = to_sun50i_domain(domain);
+
+ free_pages((unsigned long)sun50i_domain->dt, get_order(DT_SIZE));
+ sun50i_domain->dt = NULL;
+
+ iommu_put_dma_cookie(domain);
+
+ kfree(sun50i_domain);
+}
+
+static int sun50i_iommu_attach_domain(struct sun50i_iommu *iommu,
+ struct sun50i_iommu_domain *sun50i_domain)
+{
+ iommu->domain = &sun50i_domain->domain;
+ sun50i_domain->iommu = iommu;
+
+ sun50i_domain->dt_dma = dma_map_single(iommu->dev, sun50i_domain->dt,
+ DT_SIZE, DMA_TO_DEVICE);
+ if (dma_mapping_error(iommu->dev, sun50i_domain->dt_dma)) {
+ dev_err(iommu->dev, "Couldn't map L1 Page Table\n");
+ return -ENOMEM;
+ }
+
+ return sun50i_iommu_enable(iommu);
+}
+
+static void sun50i_iommu_detach_domain(struct sun50i_iommu *iommu,
+ struct sun50i_iommu_domain *sun50i_domain)
+{
+ unsigned int i;
+
+ for (i = 0; i < NUM_DT_ENTRIES; i++) {
+ phys_addr_t pt_phys;
+ u32 *page_table;
+ u32 *dte_addr;
+ u32 dte;
+
+ dte_addr = &sun50i_domain->dt[i];
+ dte = *dte_addr;
+ if (!sun50i_dte_is_pt_valid(dte))
+ continue;
+
+ memset(dte_addr, 0, sizeof(*dte_addr));
+ sun50i_table_flush(sun50i_domain, dte_addr, 1);
+
+ pt_phys = sun50i_dte_get_pt_address(dte);
+ page_table = phys_to_virt(pt_phys);
+ sun50i_iommu_free_page_table(iommu, page_table);
+ }
+
+
+ sun50i_iommu_disable(iommu);
+
+ dma_unmap_single(iommu->dev, virt_to_phys(sun50i_domain->dt),
+ DT_SIZE, DMA_TO_DEVICE);
+
+ iommu->domain = NULL;
+}
+
+static void sun50i_iommu_detach_device(struct iommu_domain *domain,
+ struct device *dev)
+{
+ struct sun50i_iommu_domain *sun50i_domain = to_sun50i_domain(domain);
+ struct sun50i_iommu *iommu = dev_iommu_priv_get(dev);
+
+ dev_dbg(dev, "Detaching from IOMMU domain\n");
+
+ if (iommu->domain != domain)
+ return;
+
+ if (refcount_dec_and_test(&sun50i_domain->refcnt))
+ sun50i_iommu_detach_domain(iommu, sun50i_domain);
+}
+
+static int sun50i_iommu_attach_device(struct iommu_domain *domain,
+ struct device *dev)
+{
+ struct sun50i_iommu_domain *sun50i_domain = to_sun50i_domain(domain);
+ struct sun50i_iommu *iommu;
+
+ iommu = sun50i_iommu_from_dev(dev);
+ if (!iommu)
+ return -ENODEV;
+
+ dev_dbg(dev, "Attaching to IOMMU domain\n");
+
+ refcount_inc(&sun50i_domain->refcnt);
+
+ if (iommu->domain == domain)
+ return 0;
+
+ if (iommu->domain)
+ sun50i_iommu_detach_device(iommu->domain, dev);
+
+ sun50i_iommu_attach_domain(iommu, sun50i_domain);
+
+ return 0;
+}
+
+static struct iommu_device *sun50i_iommu_probe_device(struct device *dev)
+{
+ struct sun50i_iommu *iommu;
+ struct iommu_group *group;
+
+ iommu = sun50i_iommu_from_dev(dev);
+ if (!iommu)
+ return ERR_PTR(-ENODEV);
+
+ return &iommu->iommu;
+}
+
+static void sun50i_iommu_release_device(struct device *dev) {}
+
+static struct iommu_group *sun50i_iommu_device_group(struct device *dev)
+{
+ struct sun50i_iommu *iommu = sun50i_iommu_from_dev(dev);
+
+ return iommu_group_ref_get(iommu->group);
+}
+
+static int sun50i_iommu_of_xlate(struct device *dev,
+ struct of_phandle_args *args)
+{
+ struct platform_device *iommu_pdev = of_find_device_by_node(args->np);
+ unsigned id = args->args[0];
+
+ dev_iommu_priv_set(dev, platform_get_drvdata(iommu_pdev));
+
+ return iommu_fwspec_add_ids(dev, &id, 1);
+}
+
+static struct iommu_ops sun50i_iommu_ops = {
+ .pgsize_bitmap = SZ_4K,
+ .attach_dev = sun50i_iommu_attach_device,
+ .detach_dev = sun50i_iommu_detach_device,
+ .device_group = sun50i_iommu_device_group,
+ .domain_alloc = sun50i_iommu_domain_alloc,
+ .domain_free = sun50i_iommu_domain_free,
+ .flush_iotlb_all = sun50i_iommu_flush_iotlb_all,
+ .iotlb_sync = sun50i_iommu_iotlb_sync,
+ .iova_to_phys = sun50i_iommu_iova_to_phys,
+ .map = sun50i_iommu_map,
+ .of_xlate = sun50i_iommu_of_xlate,
+ .probe_device = sun50i_iommu_probe_device,
+ .release_device = sun50i_iommu_release_device,
+ .unmap = sun50i_iommu_unmap,
+};
+
+static void sun50i_iommu_report_fault(struct sun50i_iommu *iommu,
+ unsigned master, phys_addr_t iova,
+ unsigned prot)
+{
+ dev_err(iommu->dev, "Page fault for %pad (master %d, dir %s)\n",
+ &iova, master, (prot == IOMMU_FAULT_WRITE) ? "wr" : "rd");
+
+ if (iommu->domain)
+ report_iommu_fault(iommu->domain, iommu->dev, iova, prot);
+ else
+ dev_err(iommu->dev, "Page fault while iommu not attached to any domain?\n");
+}
+
+static phys_addr_t sun50i_iommu_handle_pt_irq(struct sun50i_iommu *iommu,
+ unsigned addr_reg,
+ unsigned blame_reg)
+{
+ phys_addr_t iova;
+ unsigned master;
+ u32 blame;
+
+ assert_spin_locked(&iommu->iommu_lock);
+
+ iova = iommu_read(iommu, addr_reg);
+ blame = iommu_read(iommu, blame_reg);
+ master = ilog2(blame & IOMMU_INT_MASTER_MASK);
+
+ /*
+ * If the address is not in the page table, we can't get what
+ * operation triggered the fault. Assume it's a read
+ * operation.
+ */
+ sun50i_iommu_report_fault(iommu, master, iova, IOMMU_FAULT_READ);
+
+ return iova;
+}
+
+static phys_addr_t sun50i_iommu_handle_perm_irq(struct sun50i_iommu *iommu)
+{
+ enum sun50i_iommu_aci aci;
+ phys_addr_t iova;
+ unsigned master;
+ unsigned dir;
+ u32 blame;
+
+ assert_spin_locked(&iommu->iommu_lock);
+
+ blame = iommu_read(iommu, IOMMU_INT_STA_REG);
+ master = ilog2(blame & IOMMU_INT_MASTER_MASK);
+ iova = iommu_read(iommu, IOMMU_INT_ERR_ADDR_REG(master));
+ aci = sun50i_get_pte_aci(iommu_read(iommu,
+ IOMMU_INT_ERR_DATA_REG(master)));
+
+ switch (aci) {
+ /*
+ * If we are in the read-only domain, then it means we
+ * tried to write.
+ */
+ case SUN50I_IOMMU_ACI_RD:
+ dir = IOMMU_FAULT_WRITE;
+ break;
+
+ /*
+ * If we are in the write-only domain, then it means
+ * we tried to read.
+ */
+ case SUN50I_IOMMU_ACI_WR:
+
+ /*
+ * If we are in the domain without any permission, we
+ * can't really tell. Let's default to a read
+ * operation.
+ */
+ case SUN50I_IOMMU_ACI_NONE:
+
+ /* WTF? */
+ case SUN50I_IOMMU_ACI_RD_WR:
+ default:
+ dir = IOMMU_FAULT_READ;
+ break;
+ }
+
+ /*
+ * If the address is not in the page table, we can't get what
+ * operation triggered the fault. Assume it's a read
+ * operation.
+ */
+ sun50i_iommu_report_fault(iommu, master, iova, dir);
+
+ return iova;
+}
+
+static irqreturn_t sun50i_iommu_irq(int irq, void *dev_id)
+{
+ struct sun50i_iommu *iommu = dev_id;
+ phys_addr_t iova;
+ u32 status;
+
+ spin_lock(&iommu->iommu_lock);
+
+ status = iommu_read(iommu, IOMMU_INT_STA_REG);
+ if (!(status & IOMMU_INT_MASK)) {
+ spin_unlock(&iommu->iommu_lock);
+ return IRQ_NONE;
+ }
+
+ if (status & IOMMU_INT_INVALID_L2PG)
+ iova = sun50i_iommu_handle_pt_irq(iommu,
+ IOMMU_INT_ERR_ADDR_L2_REG,
+ IOMMU_L2PG_INT_REG);
+ else if (status & IOMMU_INT_INVALID_L1PG)
+ iova = sun50i_iommu_handle_pt_irq(iommu,
+ IOMMU_INT_ERR_ADDR_L1_REG,
+ IOMMU_L1PG_INT_REG);
+ else
+ iova = sun50i_iommu_handle_perm_irq(iommu);
+
+ iommu_write(iommu, IOMMU_INT_CLR_REG, status);
+
+ iommu_write(iommu, IOMMU_RESET_REG, ~status);
+ iommu_write(iommu, IOMMU_RESET_REG, status);
+
+ spin_unlock(&iommu->iommu_lock);
+
+ return IRQ_HANDLED;
+}
+
+static int sun50i_iommu_probe(struct platform_device *pdev)
+{
+ struct sun50i_iommu *iommu;
+ int ret, irq;
+
+ iommu = devm_kzalloc(&pdev->dev, sizeof(*iommu), GFP_KERNEL);
+ if (!iommu)
+ return -ENOMEM;
+ spin_lock_init(&iommu->iommu_lock);
+ platform_set_drvdata(pdev, iommu);
+ iommu->dev = &pdev->dev;
+
+ iommu->pt_pool = kmem_cache_create(dev_name(&pdev->dev),
+ PT_SIZE, PT_SIZE,
+ SLAB_HWCACHE_ALIGN,
+ NULL);
+ if (!iommu->pt_pool)
+ return -ENOMEM;
+
+ iommu->group = iommu_group_alloc();
+ if (IS_ERR(iommu->group)) {
+ ret = PTR_ERR(iommu->group);
+ goto err_free_cache;
+ }
+
+ iommu->base = devm_platform_ioremap_resource(pdev, 0);
+ if (!iommu->base) {
+ ret = PTR_ERR(iommu->base);
+ goto err_free_group;
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0) {
+ ret = irq;
+ goto err_free_group;
+ }
+
+ iommu->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(iommu->clk)) {
+ dev_err(&pdev->dev, "Couldn't get our clock.\n");
+ ret = PTR_ERR(iommu->clk);
+ goto err_free_group;
+ }
+
+ iommu->reset = devm_reset_control_get(&pdev->dev, NULL);
+ if (IS_ERR(iommu->reset)) {
+ dev_err(&pdev->dev, "Couldn't get our reset line.\n");
+ ret = PTR_ERR(iommu->reset);
+ goto err_free_group;
+ }
+
+ ret = iommu_device_sysfs_add(&iommu->iommu, &pdev->dev,
+ NULL, dev_name(&pdev->dev));
+ if (ret)
+ goto err_free_group;
+
+ iommu_device_set_ops(&iommu->iommu, &sun50i_iommu_ops);
+ iommu_device_set_fwnode(&iommu->iommu, &pdev->dev.of_node->fwnode);
+
+ ret = iommu_device_register(&iommu->iommu);
+ if (ret)
+ goto err_remove_sysfs;
+
+ ret = devm_request_irq(&pdev->dev, irq, sun50i_iommu_irq, 0,
+ dev_name(&pdev->dev), iommu);
+ if (ret < 0)
+ goto err_unregister;
+
+ bus_set_iommu(&platform_bus_type, &sun50i_iommu_ops);
+
+ return 0;
+
+err_unregister:
+ iommu_device_unregister(&iommu->iommu);
+
+err_remove_sysfs:
+ iommu_device_sysfs_remove(&iommu->iommu);
+
+err_free_group:
+ iommu_group_put(iommu->group);
+
+err_free_cache:
+ kmem_cache_destroy(iommu->pt_pool);
+
+ return ret;
+}
+
+static const struct of_device_id sun50i_iommu_dt[] = {
+ { .compatible = "allwinner,sun50i-h6-iommu", },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, sun50i_iommu_dt);
+
+static struct platform_driver sun50i_iommu_driver = {
+ .driver = {
+ .name = "sun50i-iommu",
+ .of_match_table = sun50i_iommu_dt,
+ .suppress_bind_attrs = true,
+ }
+};
+builtin_platform_driver_probe(sun50i_iommu_driver, sun50i_iommu_probe);
+
+MODULE_DESCRIPTION("Allwinner H6 IOMMU driver");
+MODULE_AUTHOR("Maxime Ripard <maxime@cerno.tech>");
+MODULE_AUTHOR("zhuxianbin <zhuxianbin@allwinnertech.com>");
+MODULE_LICENSE("Dual BSD/GPL");
--
git-series 0.9.1
^ permalink raw reply related
* [PATCH v4 4/5] arm64: dts: allwinner: h6: Add IOMMU
From: Maxime Ripard @ 2020-05-13 14:07 UTC (permalink / raw)
To: Joerg Roedel, Chen-Yu Tsai, Maxime Ripard, Mark Rutland,
Rob Herring, Frank Rowand
Cc: devicetree, linux-arm-kernel, iommu, Maxime Ripard
In-Reply-To: <cover.b27dedd61e008ffcf55a028ccddda3bb4d21dfc8.1589378833.git-series.maxime@cerno.tech>
Now that we have a driver for the IOMMU, let's start using it.
Signed-off-by: Maxime Ripard <maxime@cerno.tech>
---
arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi b/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi
index 2e31632c6ca8..78b1361dfbb9 100644
--- a/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi
+++ b/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi
@@ -135,6 +135,7 @@
clock-names = "bus",
"mod";
resets = <&display_clocks RST_MIXER0>;
+ iommus = <&iommu 0>;
ports {
#address-cells = <1>;
@@ -413,6 +414,15 @@
#interrupt-cells = <3>;
};
+ iommu: iommu@30f0000 {
+ compatible = "allwinner,sun50i-h6-iommu";
+ reg = <0x030f0000 0x10000>;
+ interrupts = <GIC_SPI 57 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&ccu CLK_BUS_IOMMU>;
+ resets = <&ccu RST_BUS_IOMMU>;
+ #iommu-cells = <1>;
+ };
+
mmc0: mmc@4020000 {
compatible = "allwinner,sun50i-h6-mmc",
"allwinner,sun50i-a64-mmc";
--
git-series 0.9.1
^ permalink raw reply related
* [PATCH v4 2/5] dt-bindings: display: sun8i-mixer: Allow for an iommu property
From: Maxime Ripard @ 2020-05-13 14:07 UTC (permalink / raw)
To: Joerg Roedel, Chen-Yu Tsai, Maxime Ripard, Mark Rutland,
Rob Herring, Frank Rowand
Cc: devicetree, linux-arm-kernel, iommu, Maxime Ripard, Rob Herring
In-Reply-To: <cover.b27dedd61e008ffcf55a028ccddda3bb4d21dfc8.1589378833.git-series.maxime@cerno.tech>
The H6 mixer is attached to an IOMMU, so let's allow that property to be
set in the bindings.
Reviewed-by: Rob Herring <robh@kernel.org>
Signed-off-by: Maxime Ripard <maxime@cerno.tech>
---
Documentation/devicetree/bindings/display/allwinner,sun8i-a83t-de2-mixer.yaml | 3 +++
1 file changed, 3 insertions(+)
diff --git a/Documentation/devicetree/bindings/display/allwinner,sun8i-a83t-de2-mixer.yaml b/Documentation/devicetree/bindings/display/allwinner,sun8i-a83t-de2-mixer.yaml
index 1dee641e3ea1..c040eef56518 100644
--- a/Documentation/devicetree/bindings/display/allwinner,sun8i-a83t-de2-mixer.yaml
+++ b/Documentation/devicetree/bindings/display/allwinner,sun8i-a83t-de2-mixer.yaml
@@ -36,6 +36,9 @@ properties:
- const: bus
- const: mod
+ iommus:
+ maxItems: 1
+
resets:
maxItems: 1
--
git-series 0.9.1
^ permalink raw reply related
* [PATCH v4 1/5] dt-bindings: iommu: Add Allwinner H6 IOMMU bindings
From: Maxime Ripard @ 2020-05-13 14:07 UTC (permalink / raw)
To: Joerg Roedel, Chen-Yu Tsai, Maxime Ripard, Mark Rutland,
Rob Herring, Frank Rowand
Cc: devicetree, linux-arm-kernel, iommu, Maxime Ripard, Rob Herring
In-Reply-To: <cover.b27dedd61e008ffcf55a028ccddda3bb4d21dfc8.1589378833.git-series.maxime@cerno.tech>
The Allwinner H6 has introduced an IOMMU. Let's add a device tree binding
for it.
Reviewed-by: Rob Herring <robh@kernel.org>
Signed-off-by: Maxime Ripard <maxime@cerno.tech>
---
Documentation/devicetree/bindings/iommu/allwinner,sun50i-h6-iommu.yaml | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 61 insertions(+)
create mode 100644 Documentation/devicetree/bindings/iommu/allwinner,sun50i-h6-iommu.yaml
diff --git a/Documentation/devicetree/bindings/iommu/allwinner,sun50i-h6-iommu.yaml b/Documentation/devicetree/bindings/iommu/allwinner,sun50i-h6-iommu.yaml
new file mode 100644
index 000000000000..5e125cf2a88b
--- /dev/null
+++ b/Documentation/devicetree/bindings/iommu/allwinner,sun50i-h6-iommu.yaml
@@ -0,0 +1,61 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iommu/allwinner,sun50i-h6-iommu.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Allwinner H6 IOMMU Device Tree Bindings
+
+maintainers:
+ - Chen-Yu Tsai <wens@csie.org>
+ - Maxime Ripard <mripard@kernel.org>
+
+properties:
+ "#iommu-cells":
+ const: 1
+ description:
+ The content of the cell is the master ID.
+
+ compatible:
+ const: allwinner,sun50i-h6-iommu
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ clocks:
+ maxItems: 1
+
+ resets:
+ maxItems: 1
+
+required:
+ - "#iommu-cells"
+ - compatible
+ - reg
+ - interrupts
+ - clocks
+ - resets
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
+
+ #include <dt-bindings/clock/sun50i-h6-ccu.h>
+ #include <dt-bindings/reset/sun50i-h6-ccu.h>
+
+ iommu: iommu@30f0000 {
+ compatible = "allwinner,sun50i-h6-iommu";
+ reg = <0x030f0000 0x10000>;
+ interrupts = <GIC_SPI 57 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&ccu CLK_BUS_IOMMU>;
+ resets = <&ccu RST_BUS_IOMMU>;
+ #iommu-cells = <1>;
+ };
+
+...
--
git-series 0.9.1
^ permalink raw reply related
* [PATCH v4 0/5] iommu: Add Allwinner H6 IOMMU driver
From: Maxime Ripard @ 2020-05-13 14:07 UTC (permalink / raw)
To: Joerg Roedel, Chen-Yu Tsai, Maxime Ripard, Mark Rutland,
Rob Herring, Frank Rowand
Cc: devicetree, linux-arm-kernel, iommu, Maxime Ripard
Hi,
Here's a series adding support for the IOMMU introduced in the Allwinner
H6. The driver from Allwinner hints at more SoCs using it in the future
(with more masters), so we can bet on that IOMMU becoming pretty much
standard in new SoCs from Allwinner.
One thing I wasn't really sure about was how to expose the statistics
reported by the IOMMU PMU (TLB hit rates, latencies, and so on). The
Allwinner driver exposes them through custom sysfs files, while they would
be best represented through perf I guess? Anyway, I'm planning to support
them later on.
Patches 1 and 3 should go through the IOMMU tree, 2 and 5 through drm-misc
and 4 through arm-soc.
Let me know what you think,
Maxime
Changes from v3:
- Rebased on next
- Removed PTW flush on map / unmap
- Added Rob's reviewed-by
Changes from v2:
- Rebased on 5.7
- Add dt bindings patch
- Allow the identity domain to be allocated
- Add an unlikely to the check on whether a PTE already exists in map
- Remove locking and adjust the PT installation to use an atomic
operation instead
- Switch to iotlb_sync / flush_iotlb_all callback instead of flushing by
ourselves.
Changes from v1:
- Add a patch to configure the IOMMU on the virtual DRM device
- Rework the domain allocation / freeing
- Remove the runtime_pm handling to power up the device and rely on
refcounting
- use map gfp argument for kmem cache allocation
- Removed unused macros
- Switched from BIT(0) to 1 for the page table entry valid flag to make
it more obvious that it's over multiple bits.
- Switch to module_initcall
- Make accesses to the fwspec more consistant
- Removed dev_info logs
- Reworked invalidation / flushing
- Allow for compilation with COMPILE_TEST
Maxime Ripard (5):
dt-bindings: iommu: Add Allwinner H6 IOMMU bindings
dt-bindings: display: sun8i-mixer: Allow for an iommu property
iommu: Add Allwinner H6 IOMMU driver
arm64: dts: allwinner: h6: Add IOMMU
drm/sun4i: mixer: Call of_dma_configure if there's an IOMMU
Documentation/devicetree/bindings/display/allwinner,sun8i-a83t-de2-mixer.yaml | 3 +-
Documentation/devicetree/bindings/iommu/allwinner,sun50i-h6-iommu.yaml | 61 ++++-
arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi | 10 +-
drivers/gpu/drm/sun4i/sun8i_mixer.c | 13 +-
drivers/iommu/Kconfig | 9 +-
drivers/iommu/Makefile | 1 +-
drivers/iommu/sun50i-iommu.c | 1027 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
7 files changed, 1124 insertions(+)
create mode 100644 Documentation/devicetree/bindings/iommu/allwinner,sun50i-h6-iommu.yaml
create mode 100644 drivers/iommu/sun50i-iommu.c
base-commit: e098d7762d602be640c53565ceca342f81e55ad2
--
git-series 0.9.1
^ permalink raw reply
* Re: [PATCH v3 3/5] iommu: Add Allwinner H6 IOMMU driver
From: Maxime Ripard @ 2020-05-13 14:04 UTC (permalink / raw)
To: Joerg Roedel
Cc: Chen-Yu Tsai, Mark Rutland, Rob Herring, Frank Rowand, devicetree,
linux-arm-kernel, iommu
In-Reply-To: <20200513095304.GK9820@8bytes.org>
[-- Attachment #1: Type: text/plain, Size: 3226 bytes --]
Hi Jörg,
On Wed, May 13, 2020 at 11:53:04AM +0200, Joerg Roedel wrote:
> On Tue, May 05, 2020 at 12:09:32PM +0200, Maxime Ripard wrote:
> > +static u32 *sun50i_dte_get_page_table(struct sun50i_iommu_domain *sun50i_domain,
> > + dma_addr_t iova, gfp_t gfp)
> > +{
> > + struct sun50i_iommu *iommu = sun50i_domain->iommu;
> > + unsigned long flags;
> > + u32 *page_table;
> > + u32 *dte_addr;
> > + u32 old_dte;
> > + u32 dte;
> > +
> > + dte_addr = &sun50i_domain->dt[sun50i_iova_get_dte_index(iova)];
> > + dte = *dte_addr;
> > + if (sun50i_dte_is_pt_valid(dte)) {
> > + phys_addr_t pt_phys = sun50i_dte_get_pt_address(dte);
> > + return (u32 *)phys_to_virt(pt_phys);
> > + }
> > +
> > + page_table = sun50i_iommu_alloc_page_table(iommu, gfp);
> > + if (IS_ERR(page_table))
> > + return page_table;
> > +
> > + dte = sun50i_mk_dte(virt_to_phys(page_table));
> > + old_dte = cmpxchg(dte_addr, 0, dte);
> > + if (old_dte) {
> > + phys_addr_t installed_pt_phys =
> > + sun50i_dte_get_pt_address(old_dte);
> > + u32 *installed_pt = phys_to_virt(installed_pt_phys);
> > + u32 *drop_pt = page_table;
> > +
> > + page_table = installed_pt;
> > + dte = old_dte;
> > + sun50i_iommu_free_page_table(iommu, drop_pt);
> > + }
> > +
> > + sun50i_table_flush(sun50i_domain, page_table, PT_SIZE);
> > + sun50i_table_flush(sun50i_domain, dte_addr, 1);
> > +
> > + spin_lock_irqsave(&iommu->iommu_lock, flags);
> > + sun50i_iommu_ptw_invalidate(iommu, iova);
> > + spin_unlock_irqrestore(&iommu->iommu_lock, flags);
>
> Why is that needed, does the PTW also cache non-present entries?
The documentation is pretty sparse, so I was conservative again. I've removed it
and it seems to be working fine in all the cases I could test, so I guess we're
fine without it :)
> > +static int sun50i_iommu_add_device(struct device *dev)
> > +{
> > + struct sun50i_iommu *iommu;
> > + struct iommu_group *group;
> > +
> > + iommu = sun50i_iommu_from_dev(dev);
> > + if (!iommu)
> > + return -ENODEV;
> > +
> > + group = iommu_group_get_for_dev(dev);
> > + if (IS_ERR(group))
> > + return PTR_ERR(group);
> > +
> > + iommu_group_put(group);
> > +
> > + return 0;
> > +}
> > +
> > +static void sun50i_iommu_remove_device(struct device *dev)
> > +{
> > + iommu_group_remove_device(dev);
> > +}
>
>
> These two call-backs have been renamed in the iommu-tree to
> probe_device() and release_device() with slightly different semantics
> and function signatures. I think for this driver they should look like
> this:
>
> static struct iommu_device *sun50i_iommu_probe_device(struct device *dev)
> {
> struct sun50i_iommu *iommu;
>
> iommu = sun50i_iommu_from_dev(dev);
> if (!iommu)
> return ERR_PTR(-ENODEV);
>
> return &iommu->iommu;
> }
>
> static void sun50i_iommu_release_device(struct device *dev)
> {
> }
>
> Can you pleas rebase these patches to the 'core' branch of the
> iommu-tree and use these new call-backs?
>
> The rest of your driver looks good to me. Good work!
I've rebased it on next, and it indeed works with your suggestion. I'll resend a
new version in a short while.
Thanks!
Maxime
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply
* Re: [PATCH 1/5] spi: dt-bindings: sifive: Add missing 2nd register region
From: Mark Brown @ 2020-05-13 14:03 UTC (permalink / raw)
To: Rob Herring
Cc: devicetree, Paul Walmsley, Linux USB List,
linux-kernel@vger.kernel.org, Palmer Dabbelt, linux-riscv,
Greg Kroah-Hartman, linux-spi, linux-clk
In-Reply-To: <CAL_Jsq+KfngSTEnP3eh6Zr9H4GUuSbyZCGXs=skbwQK0j4ZJnA@mail.gmail.com>
[-- Attachment #1: Type: text/plain, Size: 416 bytes --]
On Wed, May 13, 2020 at 08:02:14AM -0500, Rob Herring wrote:
> On Wed, May 13, 2020 at 7:10 AM Mark Brown <broonie@kernel.org> wrote:
> > [1/1] spi: dt-bindings: sifive: Add missing 2nd register region
> > commit: b265b5a0ba15b6e00abce9bf162926e84b4323b4
> You missed my ask for an ack. This is a dependency for patch 5.
Ah, there was no cover letter.
Acked-by: Mark Brown <broonie@kernel.org>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply
* [PATCH 02/10] spi: dw: Add support for RX sample delay register
From: Lars Povlsen @ 2020-05-13 14:00 UTC (permalink / raw)
To: Mark Brown, SoC Team
Cc: Lars Povlsen, Microchip Linux Driver Support, linux-spi,
devicetree, linux-kernel, linux-arm-kernel, Alexandre Belloni
In-Reply-To: <20200513140031.25633-1-lars.povlsen@microchip.com>
This add support for the RX_SAMPLE_DLY register. If enabled in the
Designware IP, it allows tuning of the rx data signal by means of an
internal rx sample fifo.
The register is located at offset 0xf0, and if the option is not
enabled in the IP, changing the register will have no effect.
Reviewed-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
Signed-off-by: Lars Povlsen <lars.povlsen@microchip.com>
---
drivers/spi/spi-dw.c | 7 +++++++
drivers/spi/spi-dw.h | 2 ++
2 files changed, 9 insertions(+)
diff --git a/drivers/spi/spi-dw.c b/drivers/spi/spi-dw.c
index e572eb34a3c1a..32997f28fa5bb 100644
--- a/drivers/spi/spi-dw.c
+++ b/drivers/spi/spi-dw.c
@@ -81,6 +81,9 @@ static ssize_t dw_spi_show_regs(struct file *file, char __user *user_buf,
"DMATDLR: \t0x%08x\n", dw_readl(dws, DW_SPI_DMATDLR));
len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len,
"DMARDLR: \t0x%08x\n", dw_readl(dws, DW_SPI_DMARDLR));
+ len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len,
+ "RX_SAMPLE_DLY: \t0x%08x\n",
+ dw_readl(dws, DW_SPI_RX_SAMPLE_DLY));
len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len,
"=================================\n");
@@ -315,6 +318,10 @@ static int dw_spi_transfer_one(struct spi_controller *master,
spi_set_clk(dws, chip->clk_div);
}
+ /* Apply RX sample delay, iff requested (nonzero) */
+ if (dws->rx_sample_dly)
+ dw_writel(dws, DW_SPI_RX_SAMPLE_DLY, dws->rx_sample_dly);
+
dws->n_bytes = DIV_ROUND_UP(transfer->bits_per_word, BITS_PER_BYTE);
dws->dma_width = DIV_ROUND_UP(transfer->bits_per_word, BITS_PER_BYTE);
diff --git a/drivers/spi/spi-dw.h b/drivers/spi/spi-dw.h
index 1bf5713e047d3..ed6e47b3f50da 100644
--- a/drivers/spi/spi-dw.h
+++ b/drivers/spi/spi-dw.h
@@ -31,6 +31,7 @@
#define DW_SPI_IDR 0x58
#define DW_SPI_VERSION 0x5c
#define DW_SPI_DR 0x60
+#define DW_SPI_RX_SAMPLE_DLY 0xf0
#define DW_SPI_CS_OVERRIDE 0xf4
/* Bit fields in CTRLR0 */
@@ -111,6 +112,7 @@ struct dw_spi {
int cs_override;
u32 reg_io_width; /* DR I/O width in bytes */
+ u8 rx_sample_dly; /* RX fifo tuning (option) */
u16 bus_num;
u16 num_cs; /* supported slave numbers */
void (*set_cs)(struct spi_device *spi, bool enable);
--
2.26.2
^ permalink raw reply related
* [PATCH 03/10] spi: dw: Add support for client driver memory operations
From: Lars Povlsen @ 2020-05-13 14:00 UTC (permalink / raw)
To: Mark Brown, SoC Team
Cc: Lars Povlsen, Microchip Linux Driver Support, linux-spi,
devicetree, linux-kernel, linux-arm-kernel, Alexandre Belloni
In-Reply-To: <20200513140031.25633-1-lars.povlsen@microchip.com>
This minor change allow dw-spi drivers to register
spi_controller_mem_ops memory operations if the platform supports it.
Reviewed-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
Signed-off-by: Lars Povlsen <lars.povlsen@microchip.com>
---
drivers/spi/spi-dw.c | 3 +++
drivers/spi/spi-dw.h | 2 ++
2 files changed, 5 insertions(+)
diff --git a/drivers/spi/spi-dw.c b/drivers/spi/spi-dw.c
index 32997f28fa5bb..d0c611c42421e 100644
--- a/drivers/spi/spi-dw.c
+++ b/drivers/spi/spi-dw.c
@@ -527,6 +527,9 @@ int dw_spi_add_host(struct device *dev, struct dw_spi *dws)
/* Basic HW init */
spi_hw_init(dev, dws);
+ /* Memory ops? */
+ master->mem_ops = dws->mem_ops;
+
if (dws->dma_ops && dws->dma_ops->dma_init) {
ret = dws->dma_ops->dma_init(dws);
if (ret) {
diff --git a/drivers/spi/spi-dw.h b/drivers/spi/spi-dw.h
index ed6e47b3f50da..8ecccbde10a20 100644
--- a/drivers/spi/spi-dw.h
+++ b/drivers/spi/spi-dw.h
@@ -3,6 +3,7 @@
#define DW_SPI_HEADER_H
#include <linux/io.h>
+#include <linux/spi/spi-mem.h>
#include <linux/scatterlist.h>
/* Register offsets */
@@ -116,6 +117,7 @@ struct dw_spi {
u16 bus_num;
u16 num_cs; /* supported slave numbers */
void (*set_cs)(struct spi_device *spi, bool enable);
+ const struct spi_controller_mem_ops *mem_ops;
/* Current message transfer state info */
size_t len;
--
2.26.2
^ permalink raw reply related
* [PATCH 04/10] dt-bindings: spi: Add bindings for spi-dw-mchp
From: Lars Povlsen @ 2020-05-13 14:00 UTC (permalink / raw)
To: Mark Brown, SoC Team, Rob Herring
Cc: Lars Povlsen, Microchip Linux Driver Support, linux-spi,
devicetree, linux-kernel, linux-arm-kernel, Alexandre Belloni
In-Reply-To: <20200513140031.25633-1-lars.povlsen@microchip.com>
This add DT bindings for the Microsemi/Microchip SPI controller used
in various SoC's. It describes the "mscc,ocelot-spi" and
"mscc,jaguar2-spi" bindings.
Reviewed-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
Signed-off-by: Lars Povlsen <lars.povlsen@microchip.com>
---
.../bindings/spi/mscc,ocelot-spi.yaml | 60 +++++++++++++++++++
.../bindings/spi/snps,dw-apb-ssi.txt | 7 +--
MAINTAINERS | 1 +
3 files changed, 63 insertions(+), 5 deletions(-)
create mode 100644 Documentation/devicetree/bindings/spi/mscc,ocelot-spi.yaml
diff --git a/Documentation/devicetree/bindings/spi/mscc,ocelot-spi.yaml b/Documentation/devicetree/bindings/spi/mscc,ocelot-spi.yaml
new file mode 100644
index 0000000000000..a3ac0fa576553
--- /dev/null
+++ b/Documentation/devicetree/bindings/spi/mscc,ocelot-spi.yaml
@@ -0,0 +1,60 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: "http://devicetree.org/schemas/spi/mscc,ocelot-spi.yaml#"
+$schema: "http://devicetree.org/meta-schemas/core.yaml#"
+
+title: Microsemi Vcore-III SPI Communication Controller
+
+maintainers:
+ - Alexandre Belloni <alexandre.belloni@bootlin.com>
+ - Lars Povlsen <lars.povlsen@microchip.com>
+
+allOf:
+ - $ref: "spi-controller.yaml#"
+
+description: |
+ The Microsemi Vcore-III SPI controller is a general purpose SPI
+ controller based upon the Designware SPI controller. It uses an 8
+ byte rx/tx fifo.
+
+properties:
+ compatible:
+ enum:
+ - mscc,ocelot-spi
+ - mscc,jaguar2-spi
+
+ interrupts:
+ maxItems: 1
+
+ reg:
+ minItems: 2
+ items:
+ - description: Designware SPI registers
+ - description: CS override registers
+
+ clocks:
+ maxItems: 1
+
+ reg-io-width:
+ description: |
+ The I/O register width (in bytes) implemented by this device.
+ items:
+ enum: [ 2, 4 ]
+ maxItems: 1
+
+required:
+ - compatible
+ - reg
+ - clocks
+
+examples:
+ - |
+ spi0: spi@101000 {
+ compatible = "mscc,ocelot-spi";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <0x101000 0x100>, <0x3c 0x18>;
+ interrupts = <9>;
+ clocks = <&ahb_clk>;
+ };
diff --git a/Documentation/devicetree/bindings/spi/snps,dw-apb-ssi.txt b/Documentation/devicetree/bindings/spi/snps,dw-apb-ssi.txt
index 3ed08ee9feba4..5e1849be7bae5 100644
--- a/Documentation/devicetree/bindings/spi/snps,dw-apb-ssi.txt
+++ b/Documentation/devicetree/bindings/spi/snps,dw-apb-ssi.txt
@@ -1,10 +1,8 @@
Synopsys DesignWare AMBA 2.0 Synchronous Serial Interface.
Required properties:
-- compatible : "snps,dw-apb-ssi" or "mscc,<soc>-spi", where soc is "ocelot" or
- "jaguar2", or "amazon,alpine-dw-apb-ssi"
-- reg : The register base for the controller. For "mscc,<soc>-spi", a second
- register set is required (named ICPU_CFG:SPI_MST)
+- compatible : "snps,dw-apb-ssi" or "amazon,alpine-dw-apb-ssi"
+- reg : The register base for the controller.
- interrupts : One interrupt, used by the controller.
- #address-cells : <1>, as required by generic SPI binding.
- #size-cells : <0>, also as required by generic SPI binding.
@@ -38,4 +36,3 @@ Example:
cs-gpios = <&gpio0 13 0>,
<&gpio0 14 0>;
};
-
diff --git a/MAINTAINERS b/MAINTAINERS
index 1db598723a1d8..6472240b8391b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11231,6 +11231,7 @@ L: linux-mips@vger.kernel.org
S: Supported
F: Documentation/devicetree/bindings/mips/mscc.txt
F: Documentation/devicetree/bindings/power/reset/ocelot-reset.txt
+F: Documentation/devicetree/bindings/spi/mscc,ocelot-spi.yaml
F: arch/mips/boot/dts/mscc/
F: arch/mips/configs/generic/board-ocelot.config
F: arch/mips/generic/board-ocelot.c
--
2.26.2
^ permalink raw reply related
* [PATCH 09/10] arm64: dts: sparx5: Add spi-nor support
From: Lars Povlsen @ 2020-05-13 14:00 UTC (permalink / raw)
To: Mark Brown, SoC Team
Cc: Lars Povlsen, Microchip Linux Driver Support, linux-spi,
devicetree, linux-kernel, linux-arm-kernel, Alexandre Belloni
In-Reply-To: <20200513140031.25633-1-lars.povlsen@microchip.com>
This add spi-nor device nodes to the Sparx5 reference boards.
Reviewed-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
Signed-off-by: Lars Povlsen <lars.povlsen@microchip.com>
---
arch/arm64/boot/dts/microchip/sparx5.dtsi | 5 +++--
arch/arm64/boot/dts/microchip/sparx5_pcb125.dts | 9 +++++++++
arch/arm64/boot/dts/microchip/sparx5_pcb134_board.dtsi | 9 +++++++++
arch/arm64/boot/dts/microchip/sparx5_pcb135_board.dtsi | 9 +++++++++
4 files changed, 30 insertions(+), 2 deletions(-)
diff --git a/arch/arm64/boot/dts/microchip/sparx5.dtsi b/arch/arm64/boot/dts/microchip/sparx5.dtsi
index daa216978887d..330fd8b096d4c 100644
--- a/arch/arm64/boot/dts/microchip/sparx5.dtsi
+++ b/arch/arm64/boot/dts/microchip/sparx5.dtsi
@@ -155,8 +155,9 @@ spi0: spi@600104000 {
reg-io-width = <4>;
reg-shift = <2>;
clocks = <&ahb_clk>;
- interrupts = <GIC_SPI 13 IRQ_TYPE_LEVEL_HIGH>;
-
+ /* NB: Polled mode - next line commented out
+ * interrupts = <GIC_SPI 13 IRQ_TYPE_LEVEL_HIGH>;
+ */
status = "disabled";
};
diff --git a/arch/arm64/boot/dts/microchip/sparx5_pcb125.dts b/arch/arm64/boot/dts/microchip/sparx5_pcb125.dts
index 573309fe45823..d8b5d23abfab0 100644
--- a/arch/arm64/boot/dts/microchip/sparx5_pcb125.dts
+++ b/arch/arm64/boot/dts/microchip/sparx5_pcb125.dts
@@ -39,6 +39,15 @@ &sdhci0 {
microchip,clock-delay = <10>;
};
+&spi0 {
+ status = "okay";
+ spi-flash@0 {
+ compatible = "jedec,spi-nor";
+ spi-max-frequency = <8000000>; /* input clock */
+ reg = <0>; /* CS0 */
+ };
+};
+
&i2c1 {
status = "okay";
};
diff --git a/arch/arm64/boot/dts/microchip/sparx5_pcb134_board.dtsi b/arch/arm64/boot/dts/microchip/sparx5_pcb134_board.dtsi
index 18a535a043686..628a05d3f57ce 100644
--- a/arch/arm64/boot/dts/microchip/sparx5_pcb134_board.dtsi
+++ b/arch/arm64/boot/dts/microchip/sparx5_pcb134_board.dtsi
@@ -38,6 +38,15 @@ gpio-restart {
};
};
+&spi0 {
+ status = "okay";
+ spi-flash@0 {
+ compatible = "jedec,spi-nor";
+ spi-max-frequency = <8000000>;
+ reg = <0>;
+ };
+};
+
&gpio {
i2cmux_pins_i: i2cmux-pins-i {
pins = "GPIO_16", "GPIO_17", "GPIO_18", "GPIO_19",
diff --git a/arch/arm64/boot/dts/microchip/sparx5_pcb135_board.dtsi b/arch/arm64/boot/dts/microchip/sparx5_pcb135_board.dtsi
index d71f11a10b3d2..fb0bc3b241204 100644
--- a/arch/arm64/boot/dts/microchip/sparx5_pcb135_board.dtsi
+++ b/arch/arm64/boot/dts/microchip/sparx5_pcb135_board.dtsi
@@ -51,6 +51,15 @@ i2cmux_s32: i2cmux-3 {
};
};
+&spi0 {
+ status = "okay";
+ spi-flash@0 {
+ compatible = "jedec,spi-nor";
+ spi-max-frequency = <8000000>;
+ reg = <0>;
+ };
+};
+
&axi {
i2c0_imux: i2c0-imux@0 {
compatible = "i2c-mux-pinctrl";
--
2.26.2
^ permalink raw reply related
* [PATCH 08/10] arm64: dts: sparx5: Add SPI controller
From: Lars Povlsen @ 2020-05-13 14:00 UTC (permalink / raw)
To: Mark Brown, SoC Team
Cc: Lars Povlsen, Microchip Linux Driver Support, linux-spi,
devicetree, linux-kernel, linux-arm-kernel, Alexandre Belloni
In-Reply-To: <20200513140031.25633-1-lars.povlsen@microchip.com>
This adds a SPI controller to the Microchip Sparx5 SoC
Reviewed-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
Signed-off-by: Lars Povlsen <lars.povlsen@microchip.com>
---
arch/arm64/boot/dts/microchip/sparx5.dtsi | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/arch/arm64/boot/dts/microchip/sparx5.dtsi b/arch/arm64/boot/dts/microchip/sparx5.dtsi
index b5f2d088af30e..daa216978887d 100644
--- a/arch/arm64/boot/dts/microchip/sparx5.dtsi
+++ b/arch/arm64/boot/dts/microchip/sparx5.dtsi
@@ -14,6 +14,7 @@ / {
#size-cells = <1>;
aliases {
+ spi0 = &spi0;
serial0 = &uart0;
serial1 = &uart1;
};
@@ -144,6 +145,21 @@ uart1: serial@600102000 {
status = "disabled";
};
+ spi0: spi@600104000 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ compatible = "microchip,sparx5-spi";
+ reg = <0x6 0x00104000 0x40>, <0 0 0>,
+ <0x3 0x0 0x4000000>;
+ num-cs = <16>;
+ reg-io-width = <4>;
+ reg-shift = <2>;
+ clocks = <&ahb_clk>;
+ interrupts = <GIC_SPI 13 IRQ_TYPE_LEVEL_HIGH>;
+
+ status = "disabled";
+ };
+
timer1: timer@600105000 {
compatible = "snps,dw-apb-timer";
reg = <0x6 0x00105000 0x1000>;
--
2.26.2
^ permalink raw reply related
* [PATCH 10/10] arm64: dts: sparx5: Add spi-nand devices
From: Lars Povlsen @ 2020-05-13 14:00 UTC (permalink / raw)
To: Mark Brown, SoC Team
Cc: Lars Povlsen, Microchip Linux Driver Support, linux-spi,
devicetree, linux-kernel, linux-arm-kernel, Alexandre Belloni
In-Reply-To: <20200513140031.25633-1-lars.povlsen@microchip.com>
This patch add spi-nand DT nodes to the applicable Sparx5 boards.
Reviewed-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
Signed-off-by: Lars Povlsen <lars.povlsen@microchip.com>
---
arch/arm64/boot/dts/microchip/sparx5.dtsi | 20 ++++++++++++++++
.../boot/dts/microchip/sparx5_pcb125.dts | 7 ++++++
.../boot/dts/microchip/sparx5_pcb134.dts | 22 ++++++++++++++++++
.../boot/dts/microchip/sparx5_pcb135.dts | 23 +++++++++++++++++++
4 files changed, 72 insertions(+)
diff --git a/arch/arm64/boot/dts/microchip/sparx5.dtsi b/arch/arm64/boot/dts/microchip/sparx5.dtsi
index 330fd8b096d4c..60629861a5157 100644
--- a/arch/arm64/boot/dts/microchip/sparx5.dtsi
+++ b/arch/arm64/boot/dts/microchip/sparx5.dtsi
@@ -193,6 +193,26 @@ gpio: pinctrl@6110101e0 {
interrupts = <GIC_SPI 20 IRQ_TYPE_LEVEL_HIGH>;
#interrupt-cells = <2>;
+ cs1_pins: cs1-pins {
+ pins = "GPIO_16";
+ function = "si";
+ };
+
+ cs2_pins: cs2-pins {
+ pins = "GPIO_17";
+ function = "si";
+ };
+
+ cs3_pins: cs3-pins {
+ pins = "GPIO_18";
+ function = "si";
+ };
+
+ si2_pins: si2-pins {
+ pins = "GPIO_39", "GPIO_40", "GPIO_41";
+ function = "si2";
+ };
+
uart_pins: uart-pins {
pins = "GPIO_10", "GPIO_11";
function = "uart";
diff --git a/arch/arm64/boot/dts/microchip/sparx5_pcb125.dts b/arch/arm64/boot/dts/microchip/sparx5_pcb125.dts
index d8b5d23abfab0..94c4c3fd5a786 100644
--- a/arch/arm64/boot/dts/microchip/sparx5_pcb125.dts
+++ b/arch/arm64/boot/dts/microchip/sparx5_pcb125.dts
@@ -46,6 +46,13 @@ spi-flash@0 {
spi-max-frequency = <8000000>; /* input clock */
reg = <0>; /* CS0 */
};
+ spi-flash@1 {
+ compatible = "spi-nand";
+ pinctrl-0 = <&cs1_pins>;
+ pinctrl-names = "default";
+ spi-max-frequency = <8000000>;
+ reg = <1>; /* CS1 */
+ };
};
&i2c1 {
diff --git a/arch/arm64/boot/dts/microchip/sparx5_pcb134.dts b/arch/arm64/boot/dts/microchip/sparx5_pcb134.dts
index feee4e99ff57c..9e8dc725a954a 100644
--- a/arch/arm64/boot/dts/microchip/sparx5_pcb134.dts
+++ b/arch/arm64/boot/dts/microchip/sparx5_pcb134.dts
@@ -15,3 +15,25 @@ memory@0 {
reg = <0x00000000 0x00000000 0x10000000>;
};
};
+
+&gpio {
+ cs14_pins: cs14-pins {
+ pins = "GPIO_44";
+ function = "si";
+ };
+};
+
+&spi0 {
+ pinctrl-0 = <&si2_pins>;
+ pinctrl-names = "default";
+ interface-mapping-mask = <0x4000>; /* NAND CS14 = SPI2 */
+ spi-rx-delay-us = <500>; /* Tune for speed */
+ /* Dedicated SPI2 interface */
+ spi-flash@e {
+ compatible = "spi-nand";
+ pinctrl-0 = <&cs14_pins>;
+ pinctrl-names = "default";
+ spi-max-frequency = <42000000>;
+ reg = <14>;
+ };
+};
diff --git a/arch/arm64/boot/dts/microchip/sparx5_pcb135.dts b/arch/arm64/boot/dts/microchip/sparx5_pcb135.dts
index 20e409a9be196..a31e10911dbaf 100644
--- a/arch/arm64/boot/dts/microchip/sparx5_pcb135.dts
+++ b/arch/arm64/boot/dts/microchip/sparx5_pcb135.dts
@@ -15,3 +15,26 @@ memory@0 {
reg = <0x00000000 0x00000000 0x10000000>;
};
};
+
+&gpio {
+ cs14_pins: cs14-pins {
+ pins = "GPIO_44";
+ function = "si";
+ };
+};
+
+&spi0 {
+ status = "okay";
+ pinctrl-0 = <&si2_pins>;
+ pinctrl-names = "default";
+ interface-mapping-mask = <0x4000>; /* NAND CS14 = SPI2 */
+ spi-rx-delay-us = <500>; /* Tune for speed */
+ /* Dedicated SPI2 interface */
+ spi-flash@e {
+ compatible = "spi-nand";
+ pinctrl-0 = <&cs14_pins>;
+ pinctrl-names = "default";
+ spi-max-frequency = <42000000>;
+ reg = <14>;
+ };
+};
--
2.26.2
^ permalink raw reply related
* [PATCH 07/10] spi: spi-dw-mchp: Add Sparx5 support
From: Lars Povlsen @ 2020-05-13 14:00 UTC (permalink / raw)
To: Mark Brown, SoC Team
Cc: Lars Povlsen, Microchip Linux Driver Support, linux-spi,
devicetree, linux-kernel, linux-arm-kernel, Alexandre Belloni
In-Reply-To: <20200513140031.25633-1-lars.povlsen@microchip.com>
This adds support for the Sparx5 SoC in the spi-dw-mchp SPI controller.
Reviewed-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
Signed-off-by: Lars Povlsen <lars.povlsen@microchip.com>
---
drivers/spi/spi-dw-mchp.c | 211 ++++++++++++++++++++++++++++++++++----
1 file changed, 189 insertions(+), 22 deletions(-)
diff --git a/drivers/spi/spi-dw-mchp.c b/drivers/spi/spi-dw-mchp.c
index 0828a7616d9ab..3abdd44a550ea 100644
--- a/drivers/spi/spi-dw-mchp.c
+++ b/drivers/spi/spi-dw-mchp.c
@@ -28,21 +28,22 @@
#define MAX_CS 4
-#define MSCC_CPU_SYSTEM_CTRL_GENERAL_CTRL 0x24
-#define OCELOT_IF_SI_OWNER_OFFSET 4
-#define JAGUAR2_IF_SI_OWNER_OFFSET 6
#define MSCC_IF_SI_OWNER_MASK GENMASK(1, 0)
#define MSCC_IF_SI_OWNER_SISL 0
#define MSCC_IF_SI_OWNER_SIBM 1
#define MSCC_IF_SI_OWNER_SIMC 2
#define MSCC_SPI_MST_SW_MODE 0x14
-#define MSCC_SPI_MST_SW_MODE_SW_PIN_CTRL_MODE BIT(13)
-#define MSCC_SPI_MST_SW_MODE_SW_SPI_CS(x) (x << 5)
struct dw_spi_mchp_props {
const char *syscon_name;
- u32 si_owner_bit;
+ u32 general_ctrl_off;
+ u32 si_owner_bit, si_owner2_bit;
+ u32 pinctrl_bit_off;
+ u32 cs_bit_off;
+ u32 ss_force_ena_off;
+ u32 ss_force_val_off;
+ u32 bootmaster_cs;
};
struct dw_spi_mchp {
@@ -53,44 +54,176 @@ struct dw_spi_mchp {
void __iomem *spi_mst;
const struct dw_spi_mchp_props *props;
u32 gen_owner;
+ u32 if2mask;
};
static const struct dw_spi_mchp_props dw_spi_mchp_props_ocelot = {
.syscon_name = "mscc,ocelot-cpu-syscon",
+ .general_ctrl_off = 0x24,
.si_owner_bit = 4,
+ .pinctrl_bit_off = 13,
+ .cs_bit_off = 5,
+ .bootmaster_cs = 0,
};
static const struct dw_spi_mchp_props dw_spi_mchp_props_jaguar2 = {
.syscon_name = "mscc,ocelot-cpu-syscon",
+ .general_ctrl_off = 0x24,
.si_owner_bit = 6,
+ .pinctrl_bit_off = 13,
+ .cs_bit_off = 5,
+ .bootmaster_cs = 0,
+};
+
+static const struct dw_spi_mchp_props dw_spi_mchp_props_sparx5 = {
+ .syscon_name = "microchip,sparx5-cpu-syscon",
+ .general_ctrl_off = 0x88,
+ .si_owner_bit = 6,
+ .si_owner2_bit = 4,
+ .ss_force_ena_off = 0xa4,
+ .ss_force_val_off = 0xa8,
+ .bootmaster_cs = 0,
};
/*
- * The Designware SPI controller (referred to as master in the documentation)
- * automatically deasserts chip select when the tx fifo is empty. The chip
- * selects then needs to be either driven as GPIOs or, for the first 4 using the
- * the SPI boot controller registers. the final chip select is an OR gate
- * between the Designware SPI controller and the SPI boot controller.
+ * Set the owner of the SPI interface
*/
-static void dw_spi_mchp_set_cs(struct spi_device *spi, bool enable)
+static void dw_spi_mchp_set_owner(struct dw_spi_mchp *dwsmchp,
+ const struct dw_spi_mchp_props *props,
+ u8 owner, u8 owner2)
+{
+ u32 val, msk;
+
+ val = (owner << props->si_owner_bit);
+ msk = (MSCC_IF_SI_OWNER_MASK << props->si_owner_bit);
+ if (props->si_owner2_bit) {
+ val |= owner2 << props->si_owner2_bit;
+ msk |= (MSCC_IF_SI_OWNER_MASK << props->si_owner2_bit);
+ }
+ if (dwsmchp->gen_owner != val) {
+ regmap_update_bits(dwsmchp->syscon, props->general_ctrl_off,
+ msk, val);
+ dwsmchp->gen_owner = val;
+ }
+}
+
+static void dw_spi_mchp_set_cs_owner(struct dw_spi_mchp *dwsmchp,
+ const struct dw_spi_mchp_props *props,
+ u8 cs, u8 owner)
{
+ u8 dummy = (owner == MSCC_IF_SI_OWNER_SIBM ?
+ MSCC_IF_SI_OWNER_SIMC : MSCC_IF_SI_OWNER_SIBM);
+ if (props->si_owner2_bit && (dwsmchp->if2mask & BIT(cs))) {
+ /* SPI2 */
+ dw_spi_mchp_set_owner(dwsmchp, props, dummy, owner);
+ } else {
+ /* SPI1 */
+ dw_spi_mchp_set_owner(dwsmchp, props, owner, dummy);
+ }
+}
+
+/*
+ * The Designware SPI controller (referred to as master in the
+ * documentation) automatically deasserts chip select when the tx fifo
+ * is empty. The chip selects then needs to be either driven as GPIOs
+ * or, for the first 4 using the the SPI boot controller
+ * registers. the final chip select is an OR gate between the
+ * Designware SPI controller and the SPI boot controller. nselect is
+ * an active low signal
+ */
+static void dw_spi_mchp_set_cs(struct spi_device *spi, bool nEnable)
+{
+ bool enable = !nEnable; /* This keeps changing in the API... */
struct dw_spi *dws = spi_master_get_devdata(spi->master);
struct dw_spi_mchp *dwsmchp = container_of(dws, struct dw_spi_mchp,
dws);
- u32 cs = spi->chip_select;
+ const struct dw_spi_mchp_props *props = dwsmchp->props;
+ u8 cs = spi->chip_select;
- if (cs < 4) {
- u32 sw_mode = MSCC_SPI_MST_SW_MODE_SW_PIN_CTRL_MODE;
+ if (enable)
+ dw_spi_mchp_set_cs_owner(dwsmchp, props, cs,
+ MSCC_IF_SI_OWNER_SIMC);
- if (!enable)
- sw_mode |= MSCC_SPI_MST_SW_MODE_SW_SPI_CS(BIT(cs));
+ if (dwsmchp->spi_mst && (cs < MAX_CS)) {
+ u32 sw_mode;
+ if (enable)
+ sw_mode = BIT(props->pinctrl_bit_off) |
+ (BIT(cs) << props->cs_bit_off);
+ else
+ sw_mode = 0;
writel(sw_mode, dwsmchp->spi_mst + MSCC_SPI_MST_SW_MODE);
+ } else if (props->ss_force_ena_off) {
+ if (enable) {
+ /* Ensure CS toggles, so start off all disabled */
+ regmap_write(dwsmchp->syscon, props->ss_force_val_off,
+ ~0);
+ /* CS override drive enable */
+ regmap_write(dwsmchp->syscon, props->ss_force_ena_off,
+ 1);
+ /* Allow settle */
+ udelay(1);
+ /* Now set CSx enabled */
+ regmap_write(dwsmchp->syscon, props->ss_force_val_off,
+ ~BIT(cs));
+ } else {
+ /* CS value */
+ regmap_write(dwsmchp->syscon, props->ss_force_val_off,
+ ~0);
+ /* CS override drive disable */
+ regmap_write(dwsmchp->syscon, props->ss_force_ena_off,
+ 0);
+ }
}
- dw_spi_set_cs(spi, enable);
+ dw_spi_set_cs(spi, nEnable);
+}
+
+static int dw_mchp_bootmaster_exec_mem_op(struct spi_mem *mem,
+ const struct spi_mem_op *op)
+{
+ struct spi_device *spi = mem->spi;
+ int ret = -ENOTSUPP;
+
+ /* Only reads, addrsize 1..4 */
+ if (!op->data.nbytes || !op->addr.nbytes || op->addr.nbytes > 4 ||
+ op->data.dir != SPI_MEM_DATA_IN)
+ return ret;
+
+ /* Only handle (normal+fast) 3/4 bytes read */
+ if (op->cmd.opcode != SPINOR_OP_READ &&
+ op->cmd.opcode != SPINOR_OP_READ_FAST &&
+ op->cmd.opcode != SPINOR_OP_READ_4B &&
+ op->cmd.opcode != SPINOR_OP_READ_FAST_4B)
+ return ret;
+
+ /* CS0..3, only 16M reach */
+ if ((spi->chip_select < MAX_CS) &&
+ (op->addr.val + op->data.nbytes) < SZ_16M) {
+ struct dw_spi *dws = spi_master_get_devdata(spi->master);
+ struct dw_spi_mchp *dwsmchp = container_of(dws,
+ struct dw_spi_mchp,
+ dws);
+ const struct dw_spi_mchp_props *props = dwsmchp->props;
+ u8 __iomem *src = dwsmchp->read_map +
+ (spi->chip_select * SZ_16M) + op->addr.val;
+
+ if (props->bootmaster_cs != spi->chip_select)
+ return ret;
+
+ /* Make boot master owner of SI interface */
+ dw_spi_mchp_set_cs_owner(dwsmchp, props, spi->chip_select,
+ MSCC_IF_SI_OWNER_SIBM);
+ memcpy(op->data.buf.in, src, op->data.nbytes);
+ ret = op->data.nbytes;
+ }
+ return ret;
}
+static const struct spi_controller_mem_ops dw_mchp_bootmaster_mem_ops = {
+ .exec_op = dw_mchp_bootmaster_exec_mem_op,
+};
+
static int dw_spi_mchp_init(struct platform_device *pdev,
struct dw_spi *dws,
struct dw_spi_mchp *dwsmchp,
@@ -107,6 +240,18 @@ static int dw_spi_mchp_init(struct platform_device *pdev,
}
}
+ /* See if we have a direct read window */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
+ if (res && resource_size(res) >= (SZ_16M*MAX_CS)) {
+ void __iomem *ptr = devm_ioremap_resource(&pdev->dev, res);
+
+ if (!IS_ERR(ptr)) {
+ dwsmchp->read_map = ptr;
+ dws->mem_ops = &dw_mchp_bootmaster_mem_ops;
+ dev_info(&pdev->dev, "Enabling fast memory operations\n");
+ }
+ }
+
dwsmchp->syscon =
syscon_regmap_lookup_by_compatible(props->syscon_name);
if (IS_ERR(dwsmchp->syscon)) {
@@ -119,10 +264,9 @@ static int dw_spi_mchp_init(struct platform_device *pdev,
if (dwsmchp->spi_mst)
writel(0, dwsmchp->spi_mst + MSCC_SPI_MST_SW_MODE);
- /* Select the owner of the SI interface */
- regmap_update_bits(dwsmchp->syscon, MSCC_CPU_SYSTEM_CTRL_GENERAL_CTRL,
- MSCC_IF_SI_OWNER_MASK << props->si_owner_bit,
- MSCC_IF_SI_OWNER_SIMC << props->si_owner_bit);
+ /* SPI2 mapping bitmask */
+ device_property_read_u32(&pdev->dev, "interface-mapping-mask",
+ &dwsmchp->if2mask);
dwsmchp->dws.set_cs = dw_spi_mchp_set_cs;
@@ -180,6 +324,27 @@ static int dw_spi_mchp_probe(struct platform_device *pdev)
dws->rx_sample_dly = DIV_ROUND_UP(rx_sample_dly,
(dws->max_freq / 1000000));
+ if (pdev->dev.of_node) {
+ int i;
+
+ for (i = 0; i < dws->num_cs; i++) {
+ int cs_gpio = of_get_named_gpio(pdev->dev.of_node,
+ "cs-gpios", i);
+
+ if (cs_gpio == -EPROBE_DEFER) {
+ ret = cs_gpio;
+ goto out;
+ }
+
+ if (gpio_is_valid(cs_gpio)) {
+ ret = devm_gpio_request(&pdev->dev, cs_gpio,
+ dev_name(&pdev->dev));
+ if (ret)
+ goto out;
+ }
+ }
+ }
+
props = device_get_match_data(&pdev->dev);
if (props)
ret = dw_spi_mchp_init(pdev, dws, dwsmchp, props);
@@ -213,6 +378,8 @@ static int dw_spi_mchp_remove(struct platform_device *pdev)
static const struct of_device_id dw_spi_mchp_of_match[] = {
{ .compatible = "mscc,ocelot-spi", .data = &dw_spi_mchp_props_ocelot},
{ .compatible = "mscc,jaguar2-spi", .data = &dw_spi_mchp_props_jaguar2},
+ { .compatible = "microchip,sparx5-spi",
+ .data = &dw_spi_mchp_props_sparx5},
{ /* end of table */}
};
MODULE_DEVICE_TABLE(of, dw_spi_mchp_of_match);
--
2.26.2
^ permalink raw reply related
* [PATCH 06/10] dt-bindings: spi: spi-dw-mchp: Add Sparx5 support
From: Lars Povlsen @ 2020-05-13 14:00 UTC (permalink / raw)
To: Mark Brown, SoC Team, Rob Herring
Cc: Lars Povlsen, Microchip Linux Driver Support, linux-spi,
devicetree, linux-kernel, linux-arm-kernel, Alexandre Belloni
In-Reply-To: <20200513140031.25633-1-lars.povlsen@microchip.com>
This add DT bindings for the Sparx5 SPI driver.
Reviewed-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
Signed-off-by: Lars Povlsen <lars.povlsen@microchip.com>
---
.../bindings/spi/mscc,ocelot-spi.yaml | 49 +++++++++++++++----
1 file changed, 39 insertions(+), 10 deletions(-)
diff --git a/Documentation/devicetree/bindings/spi/mscc,ocelot-spi.yaml b/Documentation/devicetree/bindings/spi/mscc,ocelot-spi.yaml
index a3ac0fa576553..8beecde4b0880 100644
--- a/Documentation/devicetree/bindings/spi/mscc,ocelot-spi.yaml
+++ b/Documentation/devicetree/bindings/spi/mscc,ocelot-spi.yaml
@@ -23,15 +23,23 @@ properties:
enum:
- mscc,ocelot-spi
- mscc,jaguar2-spi
+ - microchip,sparx5-spi
interrupts:
maxItems: 1
reg:
minItems: 2
- items:
- - description: Designware SPI registers
- - description: CS override registers
+ maxItems: 3
+ oneOf:
+ - items:
+ - description: Designware SPI registers
+ - description: CS override registers (Not sparx5).
+ - items:
+ - description: Designware SPI registers
+ - description: CS override registers (Not sparx5).
+ - description: Direct mapped SPI read area. If provided, the
+ driver will register spi_mem_op's to take advantage of it.
clocks:
maxItems: 1
@@ -43,6 +51,23 @@ properties:
enum: [ 2, 4 ]
maxItems: 1
+ spi-rx-delay-us:
+ description: |
+ The delay (in usec) of the RX signal sample position. This can
+ be used to tne the RX timing in order to acheive higher
+ speeds. This is used for all devices on the bus.
+ default: 0
+ maxItems: 1
+
+ interface-mapping-mask:
+ description: |
+ On the Sparx5 variant, two different busses are connected to the
+ controller. This property is a mask per chip-select, indicating
+ whether the CS should go to one or the other interface.
+ $ref: /schemas/types.yaml#/definitions/uint32
+ default: 0
+ maxItems: 1
+
required:
- compatible
- reg
@@ -50,11 +75,15 @@ required:
examples:
- |
- spi0: spi@101000 {
- compatible = "mscc,ocelot-spi";
- #address-cells = <1>;
- #size-cells = <0>;
- reg = <0x101000 0x100>, <0x3c 0x18>;
- interrupts = <9>;
- clocks = <&ahb_clk>;
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+ spi0: spi@600104000 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ compatible = "microchip,sparx5-spi";
+ reg = <0x00104000 0x40>, <0 0>, <0x3000000 0x4000000>;
+ num-cs = <16>;
+ reg-io-width = <4>;
+ reg-shift = <2>;
+ clocks = <&ahb_clk>;
+ interrupts = <GIC_SPI 13 IRQ_TYPE_LEVEL_HIGH>;
};
--
2.26.2
^ permalink raw reply related
* [PATCH 05/10] spi: spi-dw-mmio: Spin off MSCC platforms into spi-dw-mchp
From: Lars Povlsen @ 2020-05-13 14:00 UTC (permalink / raw)
To: Mark Brown, SoC Team
Cc: Lars Povlsen, Microchip Linux Driver Support, linux-spi,
devicetree, linux-kernel, linux-arm-kernel, Alexandre Belloni
In-Reply-To: <20200513140031.25633-1-lars.povlsen@microchip.com>
This patch spins off the MSCC platforms into a separate driver, as
adding new platforms from the MSCC/Microchip product lines will
further complicate (clutter) the original driver.
The new 'spi-dw-mchp' driver still builds on the dw-spi foundation.
Reviewed-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
Signed-off-by: Lars Povlsen <lars.povlsen@microchip.com>
---
MAINTAINERS | 1 +
arch/mips/configs/generic/board-ocelot.config | 2 +-
drivers/spi/Kconfig | 7 +
drivers/spi/Makefile | 1 +
drivers/spi/spi-dw-mchp.c | 232 ++++++++++++++++++
drivers/spi/spi-dw-mmio.c | 93 -------
6 files changed, 242 insertions(+), 94 deletions(-)
create mode 100644 drivers/spi/spi-dw-mchp.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 6472240b8391b..de64fd4548697 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2085,6 +2085,7 @@ M: Steen Hegelund <Steen.Hegelund@microchip.com>
M: Microchip Linux Driver Support <UNGLinuxDriver@microchip.com>
L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
F: arch/arm64/boot/dts/microchip/
+F: drivers/spi/spi-dw-mchp.c
N: sparx5
S: Supported
diff --git a/arch/mips/configs/generic/board-ocelot.config b/arch/mips/configs/generic/board-ocelot.config
index 7626f2a75b03f..6dbae76268a49 100644
--- a/arch/mips/configs/generic/board-ocelot.config
+++ b/arch/mips/configs/generic/board-ocelot.config
@@ -38,7 +38,7 @@ CONFIG_I2C_DESIGNWARE_PLATFORM=y
CONFIG_SPI=y
CONFIG_SPI_BITBANG=y
CONFIG_SPI_DESIGNWARE=y
-CONFIG_SPI_DW_MMIO=y
+CONFIG_SPI_DW_MCHP=y
CONFIG_SPI_SPIDEV=y
CONFIG_PINCTRL=y
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 741b9140992a8..77eb580b9f51f 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -238,6 +238,13 @@ config SPI_DW_MMIO
tristate "Memory-mapped io interface driver for DW SPI core"
depends on SPI_DESIGNWARE
+config SPI_DW_MCHP
+ tristate "Memory-mapped io interface driver using DW SPI core of MSCC SoCs"
+ default y if ARCH_SPARX5
+ default y if SOC_VCOREIII
+ select SPI_DESIGNWARE
+ select SPI_DW_MMIO
+
config SPI_DLN2
tristate "Diolan DLN-2 USB SPI adapter"
depends on MFD_DLN2
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index 28f601327f8c7..be8a52d90721b 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -37,6 +37,7 @@ obj-$(CONFIG_SPI_DAVINCI) += spi-davinci.o
obj-$(CONFIG_SPI_DLN2) += spi-dln2.o
obj-$(CONFIG_SPI_DESIGNWARE) += spi-dw.o
obj-$(CONFIG_SPI_DW_MMIO) += spi-dw-mmio.o
+obj-$(CONFIG_SPI_DW_MCHP) += spi-dw-mchp.o
obj-$(CONFIG_SPI_DW_PCI) += spi-dw-midpci.o
spi-dw-midpci-objs := spi-dw-pci.o spi-dw-mid.o
obj-$(CONFIG_SPI_EFM32) += spi-efm32.o
diff --git a/drivers/spi/spi-dw-mchp.c b/drivers/spi/spi-dw-mchp.c
new file mode 100644
index 0000000000000..0828a7616d9ab
--- /dev/null
+++ b/drivers/spi/spi-dw-mchp.c
@@ -0,0 +1,232 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Memory-mapped interface driver for MSCC SoCs
+ *
+ * Copyright (c) 2020 Microchip Technology Inc. and its subsidiaries.
+ */
+
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/spi/spi.h>
+#include <linux/mtd/spi-nor.h>
+#include <linux/spi/spi-mem.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/of_gpio.h>
+#include <linux/of_platform.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+
+#include "spi-dw.h"
+
+#define DRIVER_NAME "dw_spi_mchp"
+
+#define MAX_CS 4
+
+#define MSCC_CPU_SYSTEM_CTRL_GENERAL_CTRL 0x24
+#define OCELOT_IF_SI_OWNER_OFFSET 4
+#define JAGUAR2_IF_SI_OWNER_OFFSET 6
+#define MSCC_IF_SI_OWNER_MASK GENMASK(1, 0)
+#define MSCC_IF_SI_OWNER_SISL 0
+#define MSCC_IF_SI_OWNER_SIBM 1
+#define MSCC_IF_SI_OWNER_SIMC 2
+
+#define MSCC_SPI_MST_SW_MODE 0x14
+#define MSCC_SPI_MST_SW_MODE_SW_PIN_CTRL_MODE BIT(13)
+#define MSCC_SPI_MST_SW_MODE_SW_SPI_CS(x) (x << 5)
+
+struct dw_spi_mchp_props {
+ const char *syscon_name;
+ u32 si_owner_bit;
+};
+
+struct dw_spi_mchp {
+ struct dw_spi dws;
+ struct clk *clk;
+ void __iomem *read_map;
+ struct regmap *syscon;
+ void __iomem *spi_mst;
+ const struct dw_spi_mchp_props *props;
+ u32 gen_owner;
+};
+
+static const struct dw_spi_mchp_props dw_spi_mchp_props_ocelot = {
+ .syscon_name = "mscc,ocelot-cpu-syscon",
+ .si_owner_bit = 4,
+};
+
+static const struct dw_spi_mchp_props dw_spi_mchp_props_jaguar2 = {
+ .syscon_name = "mscc,ocelot-cpu-syscon",
+ .si_owner_bit = 6,
+};
+
+/*
+ * The Designware SPI controller (referred to as master in the documentation)
+ * automatically deasserts chip select when the tx fifo is empty. The chip
+ * selects then needs to be either driven as GPIOs or, for the first 4 using the
+ * the SPI boot controller registers. the final chip select is an OR gate
+ * between the Designware SPI controller and the SPI boot controller.
+ */
+static void dw_spi_mchp_set_cs(struct spi_device *spi, bool enable)
+{
+ struct dw_spi *dws = spi_master_get_devdata(spi->master);
+ struct dw_spi_mchp *dwsmchp = container_of(dws, struct dw_spi_mchp,
+ dws);
+ u32 cs = spi->chip_select;
+
+ if (cs < 4) {
+ u32 sw_mode = MSCC_SPI_MST_SW_MODE_SW_PIN_CTRL_MODE;
+
+ if (!enable)
+ sw_mode |= MSCC_SPI_MST_SW_MODE_SW_SPI_CS(BIT(cs));
+
+ writel(sw_mode, dwsmchp->spi_mst + MSCC_SPI_MST_SW_MODE);
+ }
+
+ dw_spi_set_cs(spi, enable);
+}
+
+static int dw_spi_mchp_init(struct platform_device *pdev,
+ struct dw_spi *dws,
+ struct dw_spi_mchp *dwsmchp,
+ const struct dw_spi_mchp_props *props)
+{
+ struct resource *res;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ if (res && resource_size(res) > 0) {
+ dwsmchp->spi_mst = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(dwsmchp->spi_mst)) {
+ dev_err(&pdev->dev, "SPI_MST region map failed\n");
+ return PTR_ERR(dwsmchp->spi_mst);
+ }
+ }
+
+ dwsmchp->syscon =
+ syscon_regmap_lookup_by_compatible(props->syscon_name);
+ if (IS_ERR(dwsmchp->syscon)) {
+ dev_err(&pdev->dev, "No syscon map %s\n", props->syscon_name);
+ return PTR_ERR(dwsmchp->syscon);
+ }
+ dwsmchp->props = props;
+
+ /* Deassert all CS */
+ if (dwsmchp->spi_mst)
+ writel(0, dwsmchp->spi_mst + MSCC_SPI_MST_SW_MODE);
+
+ /* Select the owner of the SI interface */
+ regmap_update_bits(dwsmchp->syscon, MSCC_CPU_SYSTEM_CTRL_GENERAL_CTRL,
+ MSCC_IF_SI_OWNER_MASK << props->si_owner_bit,
+ MSCC_IF_SI_OWNER_SIMC << props->si_owner_bit);
+
+ dwsmchp->dws.set_cs = dw_spi_mchp_set_cs;
+
+ return 0;
+}
+
+static int dw_spi_mchp_probe(struct platform_device *pdev)
+{
+ const struct dw_spi_mchp_props *props;
+ struct dw_spi_mchp *dwsmchp;
+ struct dw_spi *dws;
+ int ret;
+ int num_cs, rx_sample_dly;
+
+ dwsmchp = devm_kzalloc(&pdev->dev, sizeof(struct dw_spi_mchp),
+ GFP_KERNEL);
+ if (!dwsmchp)
+ return -ENOMEM;
+
+ dws = &dwsmchp->dws;
+
+ /* Get basic io resource and map it */
+ dws->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(dws->regs)) {
+ dev_err(&pdev->dev, "SPI region map failed\n");
+ return PTR_ERR(dws->regs);
+ }
+
+ dws->irq = of_irq_get(pdev->dev.of_node, 0);
+ if (dws->irq < 0)
+ dev_info(&pdev->dev, "no irq, using polled mode\n");
+
+ dwsmchp->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(dwsmchp->clk))
+ return PTR_ERR(dwsmchp->clk);
+ ret = clk_prepare_enable(dwsmchp->clk);
+ if (ret)
+ return ret;
+
+ dws->bus_num = pdev->id;
+
+ dws->max_freq = clk_get_rate(dwsmchp->clk);
+
+ device_property_read_u32(&pdev->dev, "reg-io-width",
+ &dws->reg_io_width);
+
+ num_cs = MAX_CS;
+
+ device_property_read_u32(&pdev->dev, "num-cs", &num_cs);
+
+ dws->num_cs = num_cs;
+
+ rx_sample_dly = 0;
+ device_property_read_u32(&pdev->dev, "spi-rx-delay-us", &rx_sample_dly);
+ dws->rx_sample_dly = DIV_ROUND_UP(rx_sample_dly,
+ (dws->max_freq / 1000000));
+
+ props = device_get_match_data(&pdev->dev);
+ if (props)
+ ret = dw_spi_mchp_init(pdev, dws, dwsmchp, props);
+ else
+ ret = -EINVAL;
+ if (ret)
+ goto out;
+
+ ret = dw_spi_add_host(&pdev->dev, dws);
+ if (ret)
+ goto out;
+
+ platform_set_drvdata(pdev, dwsmchp);
+ return 0;
+
+out:
+ clk_disable_unprepare(dwsmchp->clk);
+ return ret;
+}
+
+static int dw_spi_mchp_remove(struct platform_device *pdev)
+{
+ struct dw_spi_mchp *dwsmchp = platform_get_drvdata(pdev);
+
+ dw_spi_remove_host(&dwsmchp->dws);
+ clk_disable_unprepare(dwsmchp->clk);
+
+ return 0;
+}
+
+static const struct of_device_id dw_spi_mchp_of_match[] = {
+ { .compatible = "mscc,ocelot-spi", .data = &dw_spi_mchp_props_ocelot},
+ { .compatible = "mscc,jaguar2-spi", .data = &dw_spi_mchp_props_jaguar2},
+ { /* end of table */}
+};
+MODULE_DEVICE_TABLE(of, dw_spi_mchp_of_match);
+
+static struct platform_driver dw_spi_mchp_driver = {
+ .probe = dw_spi_mchp_probe,
+ .remove = dw_spi_mchp_remove,
+ .driver = {
+ .name = DRIVER_NAME,
+ .of_match_table = dw_spi_mchp_of_match,
+ },
+};
+module_platform_driver(dw_spi_mchp_driver);
+
+MODULE_AUTHOR("Lars Povlsen <lars.povlsen@microchip.com>");
+MODULE_DESCRIPTION("Memory-mapped I/O interface DW SPI driver for MSCC SoCs");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/spi/spi-dw-mmio.c b/drivers/spi/spi-dw-mmio.c
index 384a3ab6dc2d0..dc5db548fbcbc 100644
--- a/drivers/spi/spi-dw-mmio.c
+++ b/drivers/spi/spi-dw-mmio.c
@@ -32,97 +32,6 @@ struct dw_spi_mmio {
void *priv;
};
-#define MSCC_CPU_SYSTEM_CTRL_GENERAL_CTRL 0x24
-#define OCELOT_IF_SI_OWNER_OFFSET 4
-#define JAGUAR2_IF_SI_OWNER_OFFSET 6
-#define MSCC_IF_SI_OWNER_MASK GENMASK(1, 0)
-#define MSCC_IF_SI_OWNER_SISL 0
-#define MSCC_IF_SI_OWNER_SIBM 1
-#define MSCC_IF_SI_OWNER_SIMC 2
-
-#define MSCC_SPI_MST_SW_MODE 0x14
-#define MSCC_SPI_MST_SW_MODE_SW_PIN_CTRL_MODE BIT(13)
-#define MSCC_SPI_MST_SW_MODE_SW_SPI_CS(x) (x << 5)
-
-struct dw_spi_mscc {
- struct regmap *syscon;
- void __iomem *spi_mst;
-};
-
-/*
- * The Designware SPI controller (referred to as master in the documentation)
- * automatically deasserts chip select when the tx fifo is empty. The chip
- * selects then needs to be either driven as GPIOs or, for the first 4 using the
- * the SPI boot controller registers. the final chip select is an OR gate
- * between the Designware SPI controller and the SPI boot controller.
- */
-static void dw_spi_mscc_set_cs(struct spi_device *spi, bool enable)
-{
- struct dw_spi *dws = spi_master_get_devdata(spi->master);
- struct dw_spi_mmio *dwsmmio = container_of(dws, struct dw_spi_mmio, dws);
- struct dw_spi_mscc *dwsmscc = dwsmmio->priv;
- u32 cs = spi->chip_select;
-
- if (cs < 4) {
- u32 sw_mode = MSCC_SPI_MST_SW_MODE_SW_PIN_CTRL_MODE;
-
- if (!enable)
- sw_mode |= MSCC_SPI_MST_SW_MODE_SW_SPI_CS(BIT(cs));
-
- writel(sw_mode, dwsmscc->spi_mst + MSCC_SPI_MST_SW_MODE);
- }
-
- dw_spi_set_cs(spi, enable);
-}
-
-static int dw_spi_mscc_init(struct platform_device *pdev,
- struct dw_spi_mmio *dwsmmio,
- const char *cpu_syscon, u32 if_si_owner_offset)
-{
- struct dw_spi_mscc *dwsmscc;
-
- dwsmscc = devm_kzalloc(&pdev->dev, sizeof(*dwsmscc), GFP_KERNEL);
- if (!dwsmscc)
- return -ENOMEM;
-
- dwsmscc->spi_mst = devm_platform_ioremap_resource(pdev, 1);
- if (IS_ERR(dwsmscc->spi_mst)) {
- dev_err(&pdev->dev, "SPI_MST region map failed\n");
- return PTR_ERR(dwsmscc->spi_mst);
- }
-
- dwsmscc->syscon = syscon_regmap_lookup_by_compatible(cpu_syscon);
- if (IS_ERR(dwsmscc->syscon))
- return PTR_ERR(dwsmscc->syscon);
-
- /* Deassert all CS */
- writel(0, dwsmscc->spi_mst + MSCC_SPI_MST_SW_MODE);
-
- /* Select the owner of the SI interface */
- regmap_update_bits(dwsmscc->syscon, MSCC_CPU_SYSTEM_CTRL_GENERAL_CTRL,
- MSCC_IF_SI_OWNER_MASK << if_si_owner_offset,
- MSCC_IF_SI_OWNER_SIMC << if_si_owner_offset);
-
- dwsmmio->dws.set_cs = dw_spi_mscc_set_cs;
- dwsmmio->priv = dwsmscc;
-
- return 0;
-}
-
-static int dw_spi_mscc_ocelot_init(struct platform_device *pdev,
- struct dw_spi_mmio *dwsmmio)
-{
- return dw_spi_mscc_init(pdev, dwsmmio, "mscc,ocelot-cpu-syscon",
- OCELOT_IF_SI_OWNER_OFFSET);
-}
-
-static int dw_spi_mscc_jaguar2_init(struct platform_device *pdev,
- struct dw_spi_mmio *dwsmmio)
-{
- return dw_spi_mscc_init(pdev, dwsmmio, "mscc,jaguar2-cpu-syscon",
- JAGUAR2_IF_SI_OWNER_OFFSET);
-}
-
static int dw_spi_alpine_init(struct platform_device *pdev,
struct dw_spi_mmio *dwsmmio)
{
@@ -225,8 +134,6 @@ static int dw_spi_mmio_remove(struct platform_device *pdev)
static const struct of_device_id dw_spi_mmio_of_match[] = {
{ .compatible = "snps,dw-apb-ssi", },
- { .compatible = "mscc,ocelot-spi", .data = dw_spi_mscc_ocelot_init},
- { .compatible = "mscc,jaguar2-spi", .data = dw_spi_mscc_jaguar2_init},
{ .compatible = "amazon,alpine-dw-apb-ssi", .data = dw_spi_alpine_init},
{ .compatible = "renesas,rzn1-spi", },
{ /* end of table */}
--
2.26.2
^ permalink raw reply related
* [PATCH 01/10] spi: dw: Add support for polled operation via no IRQ specified in DT
From: Lars Povlsen @ 2020-05-13 14:00 UTC (permalink / raw)
To: Mark Brown, SoC Team
Cc: Lars Povlsen, Microchip Linux Driver Support, linux-spi,
devicetree, linux-kernel, linux-arm-kernel, Alexandre Belloni
In-Reply-To: <20200513140031.25633-1-lars.povlsen@microchip.com>
With this change a SPI controller can be added without having a IRQ
associated, and causing all transfers to be polled. For SPI controllers
without DMA, this can significantly improve performance by less
interrupt handling overhead.
Reviewed-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
Signed-off-by: Lars Povlsen <lars.povlsen@microchip.com>
---
drivers/spi/spi-dw.c | 21 +++++++++++++--------
1 file changed, 13 insertions(+), 8 deletions(-)
diff --git a/drivers/spi/spi-dw.c b/drivers/spi/spi-dw.c
index 31e3f866d11a7..e572eb34a3c1a 100644
--- a/drivers/spi/spi-dw.c
+++ b/drivers/spi/spi-dw.c
@@ -19,6 +19,8 @@
#include <linux/debugfs.h>
#endif
+#define VALID_IRQ(i) (i >= 0)
+
/* Slave spi_dev related */
struct chip_data {
u8 tmode; /* TR/TO/RO/EEPROM */
@@ -359,7 +361,7 @@ static int dw_spi_transfer_one(struct spi_controller *master,
spi_enable_chip(dws, 1);
return ret;
}
- } else if (!chip->poll_mode) {
+ } else if (!chip->poll_mode && VALID_IRQ(dws->irq)) {
txlevel = min_t(u16, dws->fifo_len / 2, dws->len / dws->n_bytes);
dw_writel(dws, DW_SPI_TXFLTR, txlevel);
@@ -379,7 +381,7 @@ static int dw_spi_transfer_one(struct spi_controller *master,
return ret;
}
- if (chip->poll_mode)
+ if (chip->poll_mode || !VALID_IRQ(dws->irq))
return poll_transfer(dws);
return 1;
@@ -487,11 +489,13 @@ int dw_spi_add_host(struct device *dev, struct dw_spi *dws)
spi_controller_set_devdata(master, dws);
- ret = request_irq(dws->irq, dw_spi_irq, IRQF_SHARED, dev_name(dev),
- master);
- if (ret < 0) {
- dev_err(dev, "can not get IRQ\n");
- goto err_free_master;
+ if (VALID_IRQ(dws->irq)) {
+ ret = request_irq(dws->irq, dw_spi_irq, IRQF_SHARED,
+ dev_name(dev), master);
+ if (ret < 0) {
+ dev_err(dev, "can not get IRQ\n");
+ goto err_free_master;
+ }
}
master->use_gpio_descriptors = true;
@@ -539,7 +543,8 @@ int dw_spi_add_host(struct device *dev, struct dw_spi *dws)
if (dws->dma_ops && dws->dma_ops->dma_exit)
dws->dma_ops->dma_exit(dws);
spi_enable_chip(dws, 0);
- free_irq(dws->irq, master);
+ if (VALID_IRQ(dws->irq))
+ free_irq(dws->irq, master);
err_free_master:
spi_controller_put(master);
return ret;
--
2.26.2
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox