From patchwork Wed Jan 17 23:33:16 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Alexander Graf X-Patchwork-Id: 862659 X-Patchwork-Delegate: trini@ti.com Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=lists.denx.de (client-ip=81.169.180.215; helo=lists.denx.de; envelope-from=u-boot-bounces@lists.denx.de; receiver=) Received: from lists.denx.de (dione.denx.de [81.169.180.215]) by ozlabs.org (Postfix) with ESMTP id 3zMNj03q3Kz9t2Q for ; Thu, 18 Jan 2018 10:34:28 +1100 (AEDT) Received: by lists.denx.de (Postfix, from userid 105) id A3012C21E43; Wed, 17 Jan 2018 23:34:16 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on lists.denx.de X-Spam-Level: X-Spam-Status: No, score=0.0 required=5.0 tests=none autolearn=unavailable autolearn_force=no version=3.4.0 Received: from lists.denx.de (localhost [IPv6:::1]) by lists.denx.de (Postfix) with ESMTP id DD3F1C21E13; Wed, 17 Jan 2018 23:33:21 +0000 (UTC) Received: by lists.denx.de (Postfix, from userid 105) id D6718C21C93; Wed, 17 Jan 2018 23:33:19 +0000 (UTC) Received: from mx2.suse.de (mx2.suse.de [195.135.220.15]) by lists.denx.de (Postfix) with ESMTPS id 60FA0C21C2F for ; Wed, 17 Jan 2018 23:33:19 +0000 (UTC) X-Virus-Scanned: by amavisd-new at test-mx.suse.de Received: from relay2.suse.de (charybdis-ext.suse.de [195.135.220.254]) by mx2.suse.de (Postfix) with ESMTP id B7DF3B00F; Wed, 17 Jan 2018 23:33:18 +0000 (UTC) From: Alexander Graf To: u-boot@lists.denx.de Date: Thu, 18 Jan 2018 00:33:16 +0100 Message-Id: <20180117233317.37302-2-agraf@suse.de> X-Mailer: git-send-email 2.12.3 In-Reply-To: <20180117233317.37302-1-agraf@suse.de> References: <20180117233317.37302-1-agraf@suse.de> Subject: [U-Boot] [PATCH v2 1/2] bcm283x: Add pinctrl driver X-BeenThere: u-boot@lists.denx.de X-Mailman-Version: 2.1.18 Precedence: list List-Id: U-Boot discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Errors-To: u-boot-bounces@lists.denx.de Sender: "U-Boot" The bcm283x family of SoCs have a GPIO controller that also acts as pinctrl controller. This patch introduces a new pinctrl driver that can actually properly mux devices into their device tree defined pin states and is now the primary owner of the gpio device. The previous GPIO driver gets moved into a subdevice of the pinctrl driver, bound to the same OF node. That way whenever a device asks for pinctrl support, it gets it automatically from the pinctrl driver and GPIO support is still available in the normal command line phase. Signed-off-by: Alexander Graf --- MAINTAINERS | 1 + arch/arm/mach-bcm283x/include/mach/gpio.h | 2 - board/raspberrypi/rpi/rpi.c | 5 +- configs/rpi_0_w_defconfig | 4 + configs/rpi_2_defconfig | 4 + configs/rpi_3_32b_defconfig | 4 + configs/rpi_3_defconfig | 4 + configs/rpi_defconfig | 4 + drivers/gpio/bcm2835_gpio.c | 29 ++---- drivers/pinctrl/Kconfig | 1 + drivers/pinctrl/Makefile | 1 + drivers/pinctrl/broadcom/Kconfig | 7 ++ drivers/pinctrl/broadcom/Makefile | 7 ++ drivers/pinctrl/broadcom/pinctrl-bcm283x.c | 150 +++++++++++++++++++++++++++++ 14 files changed, 200 insertions(+), 23 deletions(-) create mode 100644 drivers/pinctrl/broadcom/Kconfig create mode 100644 drivers/pinctrl/broadcom/Makefile create mode 100644 drivers/pinctrl/broadcom/pinctrl-bcm283x.c diff --git a/MAINTAINERS b/MAINTAINERS index 754db5553d..1f2545191b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -100,6 +100,7 @@ F: drivers/mmc/bcm2835_sdhci.c F: drivers/serial/serial_bcm283x_mu.c F: drivers/video/bcm2835.c F: include/dm/platform_data/serial_bcm283x_mu.h +F: drivers/pinctrl/broadcom/ ARM FREESCALE IMX M: Stefano Babic diff --git a/arch/arm/mach-bcm283x/include/mach/gpio.h b/arch/arm/mach-bcm283x/include/mach/gpio.h index daaee52f81..7b4ddc9246 100644 --- a/arch/arm/mach-bcm283x/include/mach/gpio.h +++ b/arch/arm/mach-bcm283x/include/mach/gpio.h @@ -61,6 +61,4 @@ struct bcm2835_gpio_platdata { unsigned long base; }; -int bcm2835_gpio_get_func_id(struct udevice *dev, unsigned gpio); - #endif /* _BCM2835_GPIO_H_ */ diff --git a/board/raspberrypi/rpi/rpi.c b/board/raspberrypi/rpi/rpi.c index 3b7a54f519..c8924d4362 100644 --- a/board/raspberrypi/rpi/rpi.c +++ b/board/raspberrypi/rpi/rpi.c @@ -24,6 +24,7 @@ #include #endif #include +#include DECLARE_GLOBAL_DATA_PTR; @@ -430,10 +431,10 @@ static bool rpi_is_serial_active(void) * out whether it is available is to check if the RX pin is muxed. */ - if (uclass_first_device(UCLASS_GPIO, &dev) || !dev) + if (uclass_first_device(UCLASS_PINCTRL, &dev) || !dev) return true; - if (bcm2835_gpio_get_func_id(dev, serial_gpio) != BCM2835_GPIO_ALT5) + if (pinctrl_get_gpio_mux(dev, 0, serial_gpio) != BCM2835_GPIO_ALT5) return false; return true; diff --git a/configs/rpi_0_w_defconfig b/configs/rpi_0_w_defconfig index 9a6d24be22..e8df255679 100644 --- a/configs/rpi_0_w_defconfig +++ b/configs/rpi_0_w_defconfig @@ -25,3 +25,7 @@ CONFIG_SYS_WHITE_ON_BLACK=y CONFIG_CONSOLE_SCROLL_LINES=10 CONFIG_PHYS_TO_BUS=y CONFIG_OF_LIBFDT_OVERLAY=y +CONFIG_PINCTRL=y +CONFIG_PINCTRL_FULL=y +# CONFIG_PINCTRL_GENERIC is not set +CONFIG_PINCTRL_BCM283X=y diff --git a/configs/rpi_2_defconfig b/configs/rpi_2_defconfig index c45ffb65af..b30e6e144c 100644 --- a/configs/rpi_2_defconfig +++ b/configs/rpi_2_defconfig @@ -32,3 +32,7 @@ CONFIG_SYS_WHITE_ON_BLACK=y CONFIG_CONSOLE_SCROLL_LINES=10 CONFIG_PHYS_TO_BUS=y CONFIG_OF_LIBFDT_OVERLAY=y +CONFIG_PINCTRL=y +CONFIG_PINCTRL_FULL=y +# CONFIG_PINCTRL_GENERIC is not set +CONFIG_PINCTRL_BCM283X=y diff --git a/configs/rpi_3_32b_defconfig b/configs/rpi_3_32b_defconfig index f7aed35797..bb40644064 100644 --- a/configs/rpi_3_32b_defconfig +++ b/configs/rpi_3_32b_defconfig @@ -34,3 +34,7 @@ CONFIG_SYS_WHITE_ON_BLACK=y CONFIG_CONSOLE_SCROLL_LINES=10 CONFIG_PHYS_TO_BUS=y CONFIG_OF_LIBFDT_OVERLAY=y +CONFIG_PINCTRL=y +CONFIG_PINCTRL_FULL=y +# CONFIG_PINCTRL_GENERIC is not set +CONFIG_PINCTRL_BCM283X=y diff --git a/configs/rpi_3_defconfig b/configs/rpi_3_defconfig index 9416e3b8fe..8306bc251d 100644 --- a/configs/rpi_3_defconfig +++ b/configs/rpi_3_defconfig @@ -34,3 +34,7 @@ CONFIG_SYS_WHITE_ON_BLACK=y CONFIG_CONSOLE_SCROLL_LINES=10 CONFIG_PHYS_TO_BUS=y CONFIG_OF_LIBFDT_OVERLAY=y +CONFIG_PINCTRL=y +CONFIG_PINCTRL_FULL=y +# CONFIG_PINCTRL_GENERIC is not set +CONFIG_PINCTRL_BCM283X=y diff --git a/configs/rpi_defconfig b/configs/rpi_defconfig index 3bfa745c2e..a7a079ddab 100644 --- a/configs/rpi_defconfig +++ b/configs/rpi_defconfig @@ -32,3 +32,7 @@ CONFIG_SYS_WHITE_ON_BLACK=y CONFIG_CONSOLE_SCROLL_LINES=10 CONFIG_PHYS_TO_BUS=y CONFIG_OF_LIBFDT_OVERLAY=y +CONFIG_PINCTRL=y +CONFIG_PINCTRL_FULL=y +# CONFIG_PINCTRL_GENERIC is not set +CONFIG_PINCTRL_BCM283X=y diff --git a/drivers/gpio/bcm2835_gpio.c b/drivers/gpio/bcm2835_gpio.c index beaa21853a..d68f8df32d 100644 --- a/drivers/gpio/bcm2835_gpio.c +++ b/drivers/gpio/bcm2835_gpio.c @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -14,6 +15,7 @@ struct bcm2835_gpios { struct bcm2835_gpio_regs *reg; + struct udevice *pinctrl; }; static int bcm2835_gpio_direction_input(struct udevice *dev, unsigned gpio) @@ -29,7 +31,7 @@ static int bcm2835_gpio_direction_input(struct udevice *dev, unsigned gpio) return 0; } -static int bcm2835_gpio_direction_output(struct udevice *dev, unsigned gpio, +static int bcm2835_gpio_direction_output(struct udevice *dev, unsigned int gpio, int value) { struct bcm2835_gpios *gpios = dev_get_priv(dev); @@ -73,19 +75,12 @@ static int bcm2835_gpio_set_value(struct udevice *dev, unsigned gpio, return 0; } -int bcm2835_gpio_get_func_id(struct udevice *dev, unsigned gpio) -{ - struct bcm2835_gpios *gpios = dev_get_priv(dev); - u32 val; - - val = readl(&gpios->reg->gpfsel[BCM2835_GPIO_FSEL_BANK(gpio)]); - - return (val >> BCM2835_GPIO_FSEL_SHIFT(gpio) & BCM2835_GPIO_FSEL_MASK); -} - static int bcm2835_gpio_get_function(struct udevice *dev, unsigned offset) { - int funcid = bcm2835_gpio_get_func_id(dev, offset); + struct bcm2835_gpios *priv = dev_get_priv(dev); + int funcid; + + funcid = pinctrl_get_gpio_mux(priv->pinctrl, 0, offset); switch (funcid) { case BCM2835_GPIO_OUTPUT: @@ -97,7 +92,6 @@ static int bcm2835_gpio_get_function(struct udevice *dev, unsigned offset) } } - static const struct dm_gpio_ops gpio_bcm2835_ops = { .direction_input = bcm2835_gpio_direction_input, .direction_output = bcm2835_gpio_direction_output, @@ -116,15 +110,13 @@ static int bcm2835_gpio_probe(struct udevice *dev) uc_priv->gpio_count = BCM2835_GPIO_COUNT; gpios->reg = (struct bcm2835_gpio_regs *)plat->base; + /* We know we're spawned by the pinctrl driver */ + gpios->pinctrl = dev->parent; + return 0; } #if CONFIG_IS_ENABLED(OF_CONTROL) -static const struct udevice_id bcm2835_gpio_id[] = { - {.compatible = "brcm,bcm2835-gpio"}, - {} -}; - static int bcm2835_gpio_ofdata_to_platdata(struct udevice *dev) { struct bcm2835_gpio_platdata *plat = dev_get_platdata(dev); @@ -142,7 +134,6 @@ static int bcm2835_gpio_ofdata_to_platdata(struct udevice *dev) U_BOOT_DRIVER(gpio_bcm2835) = { .name = "gpio_bcm2835", .id = UCLASS_GPIO, - .of_match = of_match_ptr(bcm2835_gpio_id), .ofdata_to_platdata = of_match_ptr(bcm2835_gpio_ofdata_to_platdata), .platdata_auto_alloc_size = sizeof(struct bcm2835_gpio_platdata), .ops = &gpio_bcm2835_ops, diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig index 7e8e4b0b27..0a4dd3c0cf 100644 --- a/drivers/pinctrl/Kconfig +++ b/drivers/pinctrl/Kconfig @@ -306,5 +306,6 @@ source "drivers/pinctrl/renesas/Kconfig" source "drivers/pinctrl/uniphier/Kconfig" source "drivers/pinctrl/exynos/Kconfig" source "drivers/pinctrl/mvebu/Kconfig" +source "drivers/pinctrl/broadcom/Kconfig" endmenu diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile index 8c04028dfb..c7135d29f8 100644 --- a/drivers/pinctrl/Makefile +++ b/drivers/pinctrl/Makefile @@ -22,3 +22,4 @@ obj-$(CONFIG_ARCH_MVEBU) += mvebu/ obj-$(CONFIG_PINCTRL_SINGLE) += pinctrl-single.o obj-$(CONFIG_PINCTRL_STI) += pinctrl-sti.o obj-$(CONFIG_PINCTRL_STM32) += pinctrl_stm32.o +obj-y += broadcom/ diff --git a/drivers/pinctrl/broadcom/Kconfig b/drivers/pinctrl/broadcom/Kconfig new file mode 100644 index 0000000000..4056782213 --- /dev/null +++ b/drivers/pinctrl/broadcom/Kconfig @@ -0,0 +1,7 @@ +config PINCTRL_BCM283X + depends on ARCH_BCM283X && PINCTRL_FULL && OF_CONTROL + default y + bool "Broadcom 283x family pin control driver" + help + Support pin multiplexing and pin configuration control on + Broadcom's 283x family of SoCs. diff --git a/drivers/pinctrl/broadcom/Makefile b/drivers/pinctrl/broadcom/Makefile new file mode 100644 index 0000000000..2a1e550f88 --- /dev/null +++ b/drivers/pinctrl/broadcom/Makefile @@ -0,0 +1,7 @@ +# +# Copyright (C) 2018 Alexander Graf +# +# SPDX-License-Identifier: GPL-2.0 +# https://spdx.org/licenses + +obj-$(CONFIG_PINCTRL_BCM283X) += pinctrl-bcm283x.o diff --git a/drivers/pinctrl/broadcom/pinctrl-bcm283x.c b/drivers/pinctrl/broadcom/pinctrl-bcm283x.c new file mode 100644 index 0000000000..62c35698f5 --- /dev/null +++ b/drivers/pinctrl/broadcom/pinctrl-bcm283x.c @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2018 Alexander Graf + * + * Based on drivers/pinctrl/mvebu/pinctrl-mvebu.c and + * drivers/gpio/bcm2835_gpio.c + * + * This driver gets instantiated by the GPIO driver, because both devices + * share the same device node. + * + * SPDX-License-Identifier: GPL-2.0 + * https://spdx.org/licenses + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +DECLARE_GLOBAL_DATA_PTR; + +struct bcm283x_pinctrl_priv { + u32 *base_reg; +}; + +#define MAX_PINS_PER_BANK 16 + +static void bcm2835_gpio_set_func_id(struct udevice *dev, unsigned int gpio, + int func) +{ + struct bcm283x_pinctrl_priv *priv = dev_get_priv(dev); + int reg_offset; + int field_offset; + + reg_offset = BCM2835_GPIO_FSEL_BANK(gpio); + field_offset = BCM2835_GPIO_FSEL_SHIFT(gpio); + + clrsetbits_le32(&priv->base_reg[reg_offset], + BCM2835_GPIO_FSEL_MASK << field_offset, + (func & BCM2835_GPIO_FSEL_MASK) << field_offset); +} + +static int bcm2835_gpio_get_func_id(struct udevice *dev, unsigned int gpio) +{ + struct bcm283x_pinctrl_priv *priv = dev_get_priv(dev); + u32 val; + + val = readl(&priv->base_reg[BCM2835_GPIO_FSEL_BANK(gpio)]); + + return (val >> BCM2835_GPIO_FSEL_SHIFT(gpio) & BCM2835_GPIO_FSEL_MASK); +} + +/* + * bcm283x_pinctrl_set_state: configure pin functions. + * @dev: the pinctrl device to be configured. + * @config: the state to be configured. + * @return: 0 in success + */ +int bcm283x_pinctrl_set_state(struct udevice *dev, struct udevice *config) +{ + const void *blob = gd->fdt_blob; + int node = dev_of_offset(config); + u32 pin_arr[MAX_PINS_PER_BANK]; + u32 function; + int i, pin_count; + + pin_count = fdtdec_get_int_array_count(blob, node, + "brcm,pins", + pin_arr, + ARRAY_SIZE(pin_arr)); + if (pin_count <= 0) { + debug("Failed reading pins array for pinconfig %s (%d)\n", + config->name, pin_count); + return -EINVAL; + } + + function = fdtdec_get_int(blob, node, "brcm,function", -1); + if (function < 0) { + debug("Failed reading function for pinconfig %s (%d)\n", + config->name, function); + return -EINVAL; + } + + for (i = 0; i < pin_count; i++) + bcm2835_gpio_set_func_id(dev, pin_arr[i], function); + + return 0; +} + +static int bcm283x_pinctrl_get_gpio_mux(struct udevice *dev, int banknum, + int index) +{ + if (banknum != 0) + return -EINVAL; + + return bcm2835_gpio_get_func_id(dev, index); +} + +static const struct udevice_id bcm2835_pinctrl_id[] = { + {.compatible = "brcm,bcm2835-gpio"}, + {} +}; + +int bcm283x_pinctl_probe(struct udevice *dev) +{ + struct bcm283x_pinctrl_priv *priv; + int ret; + struct udevice *pdev; + + priv = dev_get_priv(dev); + if (!priv) { + debug("%s: Failed to get private\n", __func__); + return -EINVAL; + } + + priv->base_reg = devfdt_get_addr_ptr(dev); + if (priv->base_reg == (void *)FDT_ADDR_T_NONE) { + debug("%s: Failed to get base address\n", __func__); + return -EINVAL; + } + + /* Create GPIO device as well */ + ret = device_bind(dev, lists_driver_lookup_name("gpio_bcm2835"), + "gpio_bcm2835", NULL, dev_of_offset(dev), &pdev); + if (ret) + printf("Failed to bind GPIO driver\n"); + + return 0; +} + +static struct pinctrl_ops bcm283x_pinctrl_ops = { + .set_state = bcm283x_pinctrl_set_state, + .get_gpio_mux = bcm283x_pinctrl_get_gpio_mux, +}; + +U_BOOT_DRIVER(pinctrl_bcm283x) = { + .name = "bcm283x_pinctrl", + .id = UCLASS_PINCTRL, + .of_match = of_match_ptr(bcm2835_pinctrl_id), + .priv_auto_alloc_size = sizeof(struct bcm283x_pinctrl_priv), + .ops = &bcm283x_pinctrl_ops, + .probe = bcm283x_pinctl_probe +}; From patchwork Wed Jan 17 23:33:17 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Alexander Graf X-Patchwork-Id: 862660 X-Patchwork-Delegate: trini@ti.com Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=lists.denx.de (client-ip=81.169.180.215; helo=lists.denx.de; envelope-from=u-boot-bounces@lists.denx.de; receiver=) Received: from lists.denx.de (dione.denx.de [81.169.180.215]) by ozlabs.org (Postfix) with ESMTP id 3zMNjl0Vmrz9t2l for ; Thu, 18 Jan 2018 10:35:06 +1100 (AEDT) Received: by lists.denx.de (Postfix, from userid 105) id 891FFC21E42; Wed, 17 Jan 2018 23:34:54 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on lists.denx.de X-Spam-Level: X-Spam-Status: No, score=0.0 required=5.0 tests=none autolearn=unavailable autolearn_force=no version=3.4.0 Received: from lists.denx.de (localhost [IPv6:::1]) by lists.denx.de (Postfix) with ESMTP id D386CC21D56; Wed, 17 Jan 2018 23:33:22 +0000 (UTC) Received: by lists.denx.de (Postfix, from userid 105) id 2E37DC21C51; Wed, 17 Jan 2018 23:33:20 +0000 (UTC) Received: from mx2.suse.de (mx2.suse.de [195.135.220.15]) by lists.denx.de (Postfix) with ESMTPS id 641BBC21CA6 for ; Wed, 17 Jan 2018 23:33:19 +0000 (UTC) X-Virus-Scanned: by amavisd-new at test-mx.suse.de Received: from relay1.suse.de (charybdis-ext.suse.de [195.135.220.254]) by mx2.suse.de (Postfix) with ESMTP id B5055B00E; Wed, 17 Jan 2018 23:33:18 +0000 (UTC) From: Alexander Graf To: u-boot@lists.denx.de Date: Thu, 18 Jan 2018 00:33:17 +0100 Message-Id: <20180117233317.37302-3-agraf@suse.de> X-Mailer: git-send-email 2.12.3 In-Reply-To: <20180117233317.37302-1-agraf@suse.de> References: <20180117233317.37302-1-agraf@suse.de> Subject: [U-Boot] [PATCH v2 2/2] mmc: Add bcm2835 sdhost controller X-BeenThere: u-boot@lists.denx.de X-Mailman-Version: 2.1.18 Precedence: list List-Id: U-Boot discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Errors-To: u-boot-bounces@lists.denx.de Sender: "U-Boot" The BCM2835 family of SoCs has 2 different SD controllers: One based on the SDHCI spec and a custom, home-grown one. This patch implements a driver for the latter based on the Linux driver. This is needed so that we can make use of device trees that assume driver presence of both SD controllers. Signed-off-by: Alexander Graf --- v1 -> v2: - Remove hand written pinctrl support - Checkpatch fixes --- MAINTAINERS | 1 + drivers/mmc/Kconfig | 14 + drivers/mmc/Makefile | 1 + drivers/mmc/bcm2835_sdhost.c | 994 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1010 insertions(+) create mode 100644 drivers/mmc/bcm2835_sdhost.c diff --git a/MAINTAINERS b/MAINTAINERS index 1f2545191b..728d38aebf 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -97,6 +97,7 @@ S: Orphaned (Since 2017-07) F: arch/arm/mach-bcm283x/ F: drivers/gpio/bcm2835_gpio.c F: drivers/mmc/bcm2835_sdhci.c +F: drivers/mmc/bcm2835_sdhost.c F: drivers/serial/serial_bcm283x_mu.c F: drivers/video/bcm2835.c F: include/dm/platform_data/serial_bcm283x_mu.h diff --git a/drivers/mmc/Kconfig b/drivers/mmc/Kconfig index ab0627a8af..9b90db908b 100644 --- a/drivers/mmc/Kconfig +++ b/drivers/mmc/Kconfig @@ -256,6 +256,20 @@ config MMC_UNIPHIER This selects support for the Matsushita SD/MMC Host Controller on SocioNext UniPhier and Renesas RCar SoCs. +config MMC_BCM2835 + bool "BCM2835 family custom SD/MMC Host Controller support" + depends on ARCH_BCM283X + depends on BLK && DM_MMC + depends on OF_CONTROL + default y + help + This selects support for the custom SD host controller in the BCM2835 + family of devices. + + If you have a BCM2835 platform with SD or MMC devices, say Y here. + + If unsure, say N. + config MMC_SANDBOX bool "Sandbox MMC support" depends on SANDBOX diff --git a/drivers/mmc/Makefile b/drivers/mmc/Makefile index 64b6f21c61..42113e2603 100644 --- a/drivers/mmc/Makefile +++ b/drivers/mmc/Makefile @@ -64,3 +64,4 @@ obj-$(CONFIG_MMC_SDHCI_ZYNQ) += zynq_sdhci.o obj-$(CONFIG_MMC_SUNXI) += sunxi_mmc.o obj-$(CONFIG_MMC_UNIPHIER) += uniphier-sd.o +obj-$(CONFIG_MMC_BCM2835) += bcm2835_sdhost.o diff --git a/drivers/mmc/bcm2835_sdhost.c b/drivers/mmc/bcm2835_sdhost.c new file mode 100644 index 0000000000..ad9c7adb5a --- /dev/null +++ b/drivers/mmc/bcm2835_sdhost.c @@ -0,0 +1,994 @@ +/* + * bcm2835 sdhost driver. + * + * The 2835 has two SD controllers: The Arasan sdhci controller + * (supported by the iproc driver) and a custom sdhost controller + * (supported by this driver). + * + * The sdhci controller supports both sdcard and sdio. The sdhost + * controller supports the sdcard only, but has better performance. + * Also note that the rpi3 has sdio wifi, so driving the sdcard with + * the sdhost controller allows to use the sdhci controller for wifi + * support. + * + * The configuration is done by devicetree via pin muxing. Both + * SD controller are available on the same pins (2 pin groups = pin 22 + * to 27 + pin 48 to 53). So it's possible to use both SD controllers + * at the same time with different pin groups. + * + * This code was ported to U-Boot by + * Alexander Graf + * and is based on drivers/mmc/host/bcm2835.c in Linux which is written by + * Phil Elwell + * Copyright (C) 2015-2016 Raspberry Pi (Trading) Ltd. + * which is based on + * mmc-bcm2835.c by Gellert Weisz + * which is, in turn, based on + * sdhci-bcm2708.c by Broadcom + * sdhci-bcm2835.c by Stephen Warren and Oleksandr Tymoshenko + * sdhci.c and sdhci-pci.c by Pierre Ossman + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +DECLARE_GLOBAL_DATA_PTR; + +#define msleep(a) udelay(a * 1000) + +#define SDCMD 0x00 /* Command to SD card - 16 R/W */ +#define SDARG 0x04 /* Argument to SD card - 32 R/W */ +#define SDTOUT 0x08 /* Start value for timeout counter - 32 R/W */ +#define SDCDIV 0x0c /* Start value for clock divider - 11 R/W */ +#define SDRSP0 0x10 /* SD card response (31:0) - 32 R */ +#define SDRSP1 0x14 /* SD card response (63:32) - 32 R */ +#define SDRSP2 0x18 /* SD card response (95:64) - 32 R */ +#define SDRSP3 0x1c /* SD card response (127:96) - 32 R */ +#define SDHSTS 0x20 /* SD host status - 11 R/W */ +#define SDVDD 0x30 /* SD card power control - 1 R/W */ +#define SDEDM 0x34 /* Emergency Debug Mode - 13 R/W */ +#define SDHCFG 0x38 /* Host configuration - 2 R/W */ +#define SDHBCT 0x3c /* Host byte count (debug) - 32 R/W */ +#define SDDATA 0x40 /* Data to/from SD card - 32 R/W */ +#define SDHBLC 0x50 /* Host block count (SDIO/SDHC) - 9 R/W */ + +#define SDCMD_NEW_FLAG 0x8000 +#define SDCMD_FAIL_FLAG 0x4000 +#define SDCMD_BUSYWAIT 0x800 +#define SDCMD_NO_RESPONSE 0x400 +#define SDCMD_LONG_RESPONSE 0x200 +#define SDCMD_WRITE_CMD 0x80 +#define SDCMD_READ_CMD 0x40 +#define SDCMD_CMD_MASK 0x3f + +#define SDCDIV_MAX_CDIV 0x7ff + +#define SDHSTS_BUSY_IRPT 0x400 +#define SDHSTS_BLOCK_IRPT 0x200 +#define SDHSTS_SDIO_IRPT 0x100 +#define SDHSTS_REW_TIME_OUT 0x80 +#define SDHSTS_CMD_TIME_OUT 0x40 +#define SDHSTS_CRC16_ERROR 0x20 +#define SDHSTS_CRC7_ERROR 0x10 +#define SDHSTS_FIFO_ERROR 0x08 +/* Reserved */ +/* Reserved */ +#define SDHSTS_DATA_FLAG 0x01 + +#define SDHSTS_TRANSFER_ERROR_MASK (SDHSTS_CRC7_ERROR | \ + SDHSTS_CRC16_ERROR | \ + SDHSTS_REW_TIME_OUT | \ + SDHSTS_FIFO_ERROR) + +#define SDHSTS_ERROR_MASK (SDHSTS_CMD_TIME_OUT | \ + SDHSTS_TRANSFER_ERROR_MASK) + +#define SDHCFG_BUSY_IRPT_EN BIT(10) +#define SDHCFG_BLOCK_IRPT_EN BIT(8) +#define SDHCFG_SDIO_IRPT_EN BIT(5) +#define SDHCFG_DATA_IRPT_EN BIT(4) +#define SDHCFG_SLOW_CARD BIT(3) +#define SDHCFG_WIDE_EXT_BUS BIT(2) +#define SDHCFG_WIDE_INT_BUS BIT(1) +#define SDHCFG_REL_CMD_LINE BIT(0) + +#define SDVDD_POWER_OFF 0 +#define SDVDD_POWER_ON 1 + +#define SDEDM_FORCE_DATA_MODE BIT(19) +#define SDEDM_CLOCK_PULSE BIT(20) +#define SDEDM_BYPASS BIT(21) + +#define SDEDM_WRITE_THRESHOLD_SHIFT 9 +#define SDEDM_READ_THRESHOLD_SHIFT 14 +#define SDEDM_THRESHOLD_MASK 0x1f + +#define SDEDM_FSM_MASK 0xf +#define SDEDM_FSM_IDENTMODE 0x0 +#define SDEDM_FSM_DATAMODE 0x1 +#define SDEDM_FSM_READDATA 0x2 +#define SDEDM_FSM_WRITEDATA 0x3 +#define SDEDM_FSM_READWAIT 0x4 +#define SDEDM_FSM_READCRC 0x5 +#define SDEDM_FSM_WRITECRC 0x6 +#define SDEDM_FSM_WRITEWAIT1 0x7 +#define SDEDM_FSM_POWERDOWN 0x8 +#define SDEDM_FSM_POWERUP 0x9 +#define SDEDM_FSM_WRITESTART1 0xa +#define SDEDM_FSM_WRITESTART2 0xb +#define SDEDM_FSM_GENPULSES 0xc +#define SDEDM_FSM_WRITEWAIT2 0xd +#define SDEDM_FSM_STARTPOWDOWN 0xf + +#define SDDATA_FIFO_WORDS 16 + +#define FIFO_READ_THRESHOLD 4 +#define FIFO_WRITE_THRESHOLD 4 +#define SDDATA_FIFO_PIO_BURST 8 + +struct bcm2835_plat { + struct mmc_config cfg; + struct mmc mmc; +}; + +struct bcm2835_host { + void __iomem *ioaddr; + u32 phys_addr; + + int clock; /* Current clock speed */ + unsigned int max_clk; /* Max possible freq */ + unsigned int blocks; /* remaining PIO blocks */ + int irq; /* Device IRQ */ + + u32 ns_per_fifo_word; + + /* cached registers */ + u32 hcfg; + u32 cdiv; + + struct mmc_cmd *cmd; /* Current command */ + struct mmc_data *data; /* Current data request */ + bool data_complete:1;/* Data finished before cmd */ + bool use_busy:1; /* Wait for busy interrupt */ + bool wait_data_complete:1; /* Wait for data */ + + /* for threaded irq handler */ + bool irq_block; + bool irq_busy; + bool irq_data; + + struct udevice *dev; + struct mmc *mmc; + struct bcm2835_plat *plat; +}; + +static void bcm2835_dumpregs(struct bcm2835_host *host) +{ + dev_dbg(dev, "=========== REGISTER DUMP ===========\n"); + dev_dbg(dev, "SDCMD 0x%08x\n", readl(host->ioaddr + SDCMD)); + dev_dbg(dev, "SDARG 0x%08x\n", readl(host->ioaddr + SDARG)); + dev_dbg(dev, "SDTOUT 0x%08x\n", readl(host->ioaddr + SDTOUT)); + dev_dbg(dev, "SDCDIV 0x%08x\n", readl(host->ioaddr + SDCDIV)); + dev_dbg(dev, "SDRSP0 0x%08x\n", readl(host->ioaddr + SDRSP0)); + dev_dbg(dev, "SDRSP1 0x%08x\n", readl(host->ioaddr + SDRSP1)); + dev_dbg(dev, "SDRSP2 0x%08x\n", readl(host->ioaddr + SDRSP2)); + dev_dbg(dev, "SDRSP3 0x%08x\n", readl(host->ioaddr + SDRSP3)); + dev_dbg(dev, "SDHSTS 0x%08x\n", readl(host->ioaddr + SDHSTS)); + dev_dbg(dev, "SDVDD 0x%08x\n", readl(host->ioaddr + SDVDD)); + dev_dbg(dev, "SDEDM 0x%08x\n", readl(host->ioaddr + SDEDM)); + dev_dbg(dev, "SDHCFG 0x%08x\n", readl(host->ioaddr + SDHCFG)); + dev_dbg(dev, "SDHBCT 0x%08x\n", readl(host->ioaddr + SDHBCT)); + dev_dbg(dev, "SDHBLC 0x%08x\n", readl(host->ioaddr + SDHBLC)); + dev_dbg(dev, "===========================================\n"); +} + +static void bcm2835_reset_internal(struct bcm2835_host *host) +{ + u32 temp; + + writel(SDVDD_POWER_OFF, host->ioaddr + SDVDD); + writel(0, host->ioaddr + SDCMD); + writel(0, host->ioaddr + SDARG); + writel(0xf00000, host->ioaddr + SDTOUT); + writel(0, host->ioaddr + SDCDIV); + writel(0x7f8, host->ioaddr + SDHSTS); /* Write 1s to clear */ + writel(0, host->ioaddr + SDHCFG); + writel(0, host->ioaddr + SDHBCT); + writel(0, host->ioaddr + SDHBLC); + + /* Limit fifo usage due to silicon bug */ + temp = readl(host->ioaddr + SDEDM); + temp &= ~((SDEDM_THRESHOLD_MASK << SDEDM_READ_THRESHOLD_SHIFT) | + (SDEDM_THRESHOLD_MASK << SDEDM_WRITE_THRESHOLD_SHIFT)); + temp |= (FIFO_READ_THRESHOLD << SDEDM_READ_THRESHOLD_SHIFT) | + (FIFO_WRITE_THRESHOLD << SDEDM_WRITE_THRESHOLD_SHIFT); + writel(temp, host->ioaddr + SDEDM); + msleep(20); + writel(SDVDD_POWER_ON, host->ioaddr + SDVDD); + msleep(20); + host->clock = 0; + writel(host->hcfg, host->ioaddr + SDHCFG); + writel(host->cdiv, host->ioaddr + SDCDIV); +} + +static int bcm2835_finish_command(struct bcm2835_host *host); + +static void bcm2835_wait_transfer_complete(struct bcm2835_host *host) +{ + int timediff; + u32 alternate_idle; + + alternate_idle = (host->data->flags & MMC_DATA_READ) ? + SDEDM_FSM_READWAIT : SDEDM_FSM_WRITESTART1; + + timediff = 0; + + while (1) { + u32 edm, fsm; + + edm = readl(host->ioaddr + SDEDM); + fsm = edm & SDEDM_FSM_MASK; + + if ((fsm == SDEDM_FSM_IDENTMODE) || + (fsm == SDEDM_FSM_DATAMODE)) + break; + if (fsm == alternate_idle) { + writel(edm | SDEDM_FORCE_DATA_MODE, + host->ioaddr + SDEDM); + break; + } + + timediff++; + if (timediff == 100000) { + dev_err(host->dev, + "wait_transfer_complete - still waiting after %d retries\n", + timediff); + bcm2835_dumpregs(host); + return; + } + } +} + +static int bcm2835_transfer_block_pio(struct bcm2835_host *host, bool is_read) +{ + size_t blksize; + struct mmc_data *data = host->data; + + blksize = data->blocksize; + + while (blksize) { + int copy_words; + u32 hsts = 0; + size_t len; + u32 *buf; + + len = blksize; + if (len % 4) + return -EINVAL; + + blksize -= len; + + buf = is_read ? (u32 *)data->dest : (u32 *)data->src; + + if (is_read) + data->dest += len; + else + data->src += len; + + copy_words = len / 4; + + while (copy_words) { + int burst_words, words; + u32 edm; + + burst_words = min(SDDATA_FIFO_PIO_BURST, copy_words); + edm = readl(host->ioaddr + SDEDM); + if (is_read) + words = ((edm >> 4) & 0x1f); + else + words = SDDATA_FIFO_WORDS - ((edm >> 4) & 0x1f); + + if (words < burst_words) { + int fsm_state = (edm & SDEDM_FSM_MASK); + + if ((is_read && + (fsm_state != SDEDM_FSM_READDATA && + fsm_state != SDEDM_FSM_READWAIT && + fsm_state != SDEDM_FSM_READCRC)) || + (!is_read && + (fsm_state != SDEDM_FSM_WRITEDATA && + fsm_state != SDEDM_FSM_WRITESTART1 && + fsm_state != SDEDM_FSM_WRITESTART2))) { + hsts = readl(host->ioaddr + SDHSTS); + printf("fsm %x, hsts %08x\n", + fsm_state, hsts); + if (hsts & SDHSTS_ERROR_MASK) + break; + } + + continue; + } else if (words > copy_words) { + words = copy_words; + } + + copy_words -= words; + + while (words) { + if (is_read) + *(buf++) = readl(host->ioaddr + SDDATA); + else + writel(*(buf++), host->ioaddr + SDDATA); + words--; + } + } + + if (hsts & SDHSTS_ERROR_MASK) + break; + } + + return 0; +} + +static int bcm2835_transfer_pio(struct bcm2835_host *host) +{ + u32 sdhsts; + bool is_read; + int r = 0; + + is_read = (host->data->flags & MMC_DATA_READ) != 0; + r = bcm2835_transfer_block_pio(host, is_read); + + if (host->wait_data_complete) + bcm2835_wait_transfer_complete(host); + + sdhsts = readl(host->ioaddr + SDHSTS); + if (sdhsts & (SDHSTS_CRC16_ERROR | + SDHSTS_CRC7_ERROR | + SDHSTS_FIFO_ERROR)) { + printf("%s transfer error - HSTS %08x\n", + is_read ? "read" : "write", sdhsts); + r = -EILSEQ; + } else if ((sdhsts & (SDHSTS_CMD_TIME_OUT | + SDHSTS_REW_TIME_OUT))) { + printf("%s timeout error - HSTS %08x\n", + is_read ? "read" : "write", sdhsts); + r = -ETIMEDOUT; + } + + return r; +} + +static void bcm2835_set_transfer_irqs(struct bcm2835_host *host) +{ + u32 all_irqs = SDHCFG_DATA_IRPT_EN | SDHCFG_BLOCK_IRPT_EN | + SDHCFG_BUSY_IRPT_EN; + + host->hcfg = (host->hcfg & ~all_irqs) | + SDHCFG_DATA_IRPT_EN | + SDHCFG_BUSY_IRPT_EN; + + writel(host->hcfg, host->ioaddr + SDHCFG); +} + +static +void bcm2835_prepare_data(struct bcm2835_host *host, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + WARN_ON(host->data); + + host->data = data; + if (!data) + return; + + host->wait_data_complete = cmd->cmdidx != MMC_CMD_READ_MULTIPLE_BLOCK; + host->data_complete = false; + + /* Use PIO */ + host->blocks = data->blocks; + + bcm2835_set_transfer_irqs(host); + + writel(data->blocksize, host->ioaddr + SDHBCT); + writel(data->blocks, host->ioaddr + SDHBLC); +} + +static u32 bcm2835_read_wait_sdcmd(struct bcm2835_host *host, u32 max_ms) +{ + u32 value; + int ret; + int timeout_us = (max_ms * 1000); + + ret = readl_poll_timeout(host->ioaddr + SDCMD, value, + !(value & SDCMD_NEW_FLAG), 10); + if (ret == -ETIMEDOUT) + /* if it takes a while make poll interval bigger */ + ret = readl_poll_timeout(host->ioaddr + SDCMD, value, + !(value & SDCMD_NEW_FLAG), + timeout_us); + if (ret == -ETIMEDOUT) + printf("%s: timeout (%d ms)\n", __func__, max_ms); + + return value; +} + +static int bcm2835_send_command(struct bcm2835_host *host, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + u32 sdcmd, sdhsts; + + WARN_ON(host->cmd); + + sdcmd = bcm2835_read_wait_sdcmd(host, 100); + if (sdcmd & SDCMD_NEW_FLAG) { + printf("previous command never completed.\n"); + bcm2835_dumpregs(host); + return -EILSEQ; + } + + host->cmd = cmd; + + /* Clear any error flags */ + sdhsts = readl(host->ioaddr + SDHSTS); + if (sdhsts & SDHSTS_ERROR_MASK) + writel(sdhsts, host->ioaddr + SDHSTS); + + if ((cmd->resp_type & MMC_RSP_136) && (cmd->resp_type & MMC_RSP_BUSY)) { + printf("unsupported response type!\n"); + return -EINVAL; + } + + bcm2835_prepare_data(host, cmd, data); + + writel(cmd->cmdarg, host->ioaddr + SDARG); + + sdcmd = cmd->cmdidx & SDCMD_CMD_MASK; + + host->use_busy = false; + if (!(cmd->resp_type & MMC_RSP_PRESENT)) { + sdcmd |= SDCMD_NO_RESPONSE; + } else { + if (cmd->resp_type & MMC_RSP_136) + sdcmd |= SDCMD_LONG_RESPONSE; + if (cmd->resp_type & MMC_RSP_BUSY) { + sdcmd |= SDCMD_BUSYWAIT; + host->use_busy = true; + } + } + + if (data) { + if (data->flags & MMC_DATA_WRITE) + sdcmd |= SDCMD_WRITE_CMD; + if (data->flags & MMC_DATA_READ) + sdcmd |= SDCMD_READ_CMD; + } + + writel(sdcmd | SDCMD_NEW_FLAG, host->ioaddr + SDCMD); + + return 0; +} + +static int bcm2835_transfer_complete(struct bcm2835_host *host) +{ + int r = 0; + + WARN_ON(!host->data_complete); + + host->data = NULL; + + return r; +} + +static void bcm2835_finish_data(struct bcm2835_host *host) +{ + host->hcfg &= ~(SDHCFG_DATA_IRPT_EN | SDHCFG_BLOCK_IRPT_EN); + writel(host->hcfg, host->ioaddr + SDHCFG); + + host->data_complete = true; + + if (host->cmd) { + /* Data managed to finish before the + * command completed. Make sure we do + * things in the proper order. + */ + dev_dbg(dev, "Finished early - HSTS %08x\n", + readl(host->ioaddr + SDHSTS)); + } else { + bcm2835_transfer_complete(host); + } +} + +static int bcm2835_finish_command(struct bcm2835_host *host) +{ + struct mmc_cmd *cmd = host->cmd; + u32 sdcmd; + int r = 0; + + sdcmd = bcm2835_read_wait_sdcmd(host, 100); + + /* Check for errors */ + if (sdcmd & SDCMD_NEW_FLAG) { + printf("command never completed.\n"); + bcm2835_dumpregs(host); + return -EIO; + } else if (sdcmd & SDCMD_FAIL_FLAG) { + u32 sdhsts = readl(host->ioaddr + SDHSTS); + + /* Clear the errors */ + writel(SDHSTS_ERROR_MASK, host->ioaddr + SDHSTS); + + if (!(sdhsts & SDHSTS_CRC7_ERROR) || + (host->cmd->cmdidx != MMC_CMD_SEND_OP_COND)) { + if (sdhsts & SDHSTS_CMD_TIME_OUT) { + r = -ETIMEDOUT; + } else { + printf("unexpected command %d error\n", + host->cmd->cmdidx); + bcm2835_dumpregs(host); + r = -EILSEQ; + } + + return r; + } + } + + if (cmd->resp_type & MMC_RSP_PRESENT) { + if (cmd->resp_type & MMC_RSP_136) { + int i; + + for (i = 0; i < 4; i++) { + cmd->response[3 - i] = + readl(host->ioaddr + SDRSP0 + i * 4); + } + } else { + cmd->response[0] = readl(host->ioaddr + SDRSP0); + } + } + + /* Processed actual command. */ + host->cmd = NULL; + if (host->data && host->data_complete) + r = bcm2835_transfer_complete(host); + + return r; +} + +static int bcm2835_check_cmd_error(struct bcm2835_host *host, u32 intmask) +{ + int r = -EINVAL; + + if (!(intmask & SDHSTS_ERROR_MASK)) + return 0; + + if (!host->cmd) + return -EINVAL; + + printf("sdhost_busy_irq: intmask %08x\n", intmask); + if (intmask & SDHSTS_CRC7_ERROR) { + r = -EILSEQ; + } else if (intmask & (SDHSTS_CRC16_ERROR | + SDHSTS_FIFO_ERROR)) { + r = -EILSEQ; + } else if (intmask & SDHSTS_REW_TIME_OUT) { + r = -ETIMEDOUT; + } else if (intmask & SDHSTS_CMD_TIME_OUT) { + r = -ETIMEDOUT; + } + bcm2835_dumpregs(host); + return r; +} + +static int bcm2835_check_data_error(struct bcm2835_host *host, u32 intmask) +{ + int r = 0; + + if (!host->data) + return 0; + if (intmask & (SDHSTS_CRC16_ERROR | SDHSTS_FIFO_ERROR)) + r = -EILSEQ; + if (intmask & SDHSTS_REW_TIME_OUT) + r = -ETIMEDOUT; + + if (r) + printf("%s:%d %d\n", __func__, __LINE__, r); + + return r; +} + +static void bcm2835_busy_irq(struct bcm2835_host *host) +{ + if (WARN_ON(!host->cmd)) { + bcm2835_dumpregs(host); + return; + } + + if (WARN_ON(!host->use_busy)) { + bcm2835_dumpregs(host); + return; + } + host->use_busy = false; + + bcm2835_finish_command(host); +} + +static void bcm2835_data_irq(struct bcm2835_host *host, u32 intmask) +{ + int r; + + /* There are no dedicated data/space available interrupt + * status bits, so it is necessary to use the single shared + * data/space available FIFO status bits. It is therefore not + * an error to get here when there is no data transfer in + * progress. + */ + if (!host->data) + return; + + r = bcm2835_check_data_error(host, intmask); + if (r) + goto finished; + + if (host->data->flags & MMC_DATA_WRITE) { + /* Use the block interrupt for writes after the first block */ + host->hcfg &= ~(SDHCFG_DATA_IRPT_EN); + host->hcfg |= SDHCFG_BLOCK_IRPT_EN; + writel(host->hcfg, host->ioaddr + SDHCFG); + bcm2835_transfer_pio(host); + } else { + bcm2835_transfer_pio(host); + host->blocks--; + if ((host->blocks == 0)) + goto finished; + } + return; + +finished: + host->hcfg &= ~(SDHCFG_DATA_IRPT_EN | SDHCFG_BLOCK_IRPT_EN); + writel(host->hcfg, host->ioaddr + SDHCFG); +} + +static void bcm2835_data_threaded_irq(struct bcm2835_host *host) +{ + if (!host->data) + return; + if ((host->blocks == 0)) + bcm2835_finish_data(host); +} + +static void bcm2835_block_irq(struct bcm2835_host *host) +{ + if (WARN_ON(!host->data)) { + bcm2835_dumpregs(host); + return; + } + + WARN_ON(!host->blocks); + if ((--host->blocks == 0)) + bcm2835_finish_data(host); + else + bcm2835_transfer_pio(host); +} + +static irqreturn_t bcm2835_irq(int irq, void *dev_id) +{ + irqreturn_t result = IRQ_NONE; + struct bcm2835_host *host = dev_id; + u32 intmask; + + intmask = readl(host->ioaddr + SDHSTS); + + writel(SDHSTS_BUSY_IRPT | + SDHSTS_BLOCK_IRPT | + SDHSTS_SDIO_IRPT | + SDHSTS_DATA_FLAG, + host->ioaddr + SDHSTS); + + if (intmask & SDHSTS_BLOCK_IRPT) { + bcm2835_check_data_error(host, intmask); + host->irq_block = true; + result = IRQ_WAKE_THREAD; + } + + if (intmask & SDHSTS_BUSY_IRPT) { + if (!bcm2835_check_cmd_error(host, intmask)) { + host->irq_busy = true; + result = IRQ_WAKE_THREAD; + } else { + result = IRQ_HANDLED; + } + } + + /* There is no true data interrupt status bit, so it is + * necessary to qualify the data flag with the interrupt + * enable bit. + */ + if ((intmask & SDHSTS_DATA_FLAG) && + (host->hcfg & SDHCFG_DATA_IRPT_EN)) { + bcm2835_data_irq(host, intmask); + host->irq_data = true; + result = IRQ_WAKE_THREAD; + } + + return result; +} + +static irqreturn_t bcm2835_threaded_irq(int irq, void *dev_id) +{ + struct bcm2835_host *host = dev_id; + bool block, busy, data; + + block = host->irq_block; + busy = host->irq_busy; + data = host->irq_data; + host->irq_block = false; + host->irq_busy = false; + host->irq_data = false; + + if (block) + bcm2835_block_irq(host); + if (busy) + bcm2835_busy_irq(host); + if (data) + bcm2835_data_threaded_irq(host); + + return IRQ_HANDLED; +} + +static void bcm2835_irq_poll(struct bcm2835_host *host) +{ + u32 intmask; + + while (1) { + intmask = readl(host->ioaddr + SDHSTS); + if (intmask & (SDHSTS_BUSY_IRPT | SDHSTS_BLOCK_IRPT | + SDHSTS_SDIO_IRPT | SDHSTS_DATA_FLAG)) { + bcm2835_irq(0, host); + bcm2835_threaded_irq(0, host); + return; + } + } +} + +static void bcm2835_set_clock(struct bcm2835_host *host, unsigned int clock) +{ + int div; + + /* The SDCDIV register has 11 bits, and holds (div - 2). But + * in data mode the max is 50MHz wihout a minimum, and only + * the bottom 3 bits are used. Since the switch over is + * automatic (unless we have marked the card as slow...), + * chosen values have to make sense in both modes. Ident mode + * must be 100-400KHz, so can range check the requested + * clock. CMD15 must be used to return to data mode, so this + * can be monitored. + * + * clock 250MHz -> 0->125MHz, 1->83.3MHz, 2->62.5MHz, 3->50.0MHz + * 4->41.7MHz, 5->35.7MHz, 6->31.3MHz, 7->27.8MHz + * + * 623->400KHz/27.8MHz + * reset value (507)->491159/50MHz + * + * BUT, the 3-bit clock divisor in data mode is too small if + * the core clock is higher than 250MHz, so instead use the + * SLOW_CARD configuration bit to force the use of the ident + * clock divisor at all times. + */ + + if (clock < 100000) { + /* Can't stop the clock, but make it as slow as possible + * to show willing + */ + host->cdiv = SDCDIV_MAX_CDIV; + writel(host->cdiv, host->ioaddr + SDCDIV); + return; + } + + div = host->max_clk / clock; + if (div < 2) + div = 2; + if ((host->max_clk / div) > clock) + div++; + div -= 2; + + if (div > SDCDIV_MAX_CDIV) + div = SDCDIV_MAX_CDIV; + + clock = host->max_clk / (div + 2); + host->mmc->clock = clock; + + /* Calibrate some delays */ + + host->ns_per_fifo_word = (1000000000 / clock) * + ((host->mmc->card_caps & MMC_MODE_4BIT) ? 8 : 32); + + host->cdiv = div; + writel(host->cdiv, host->ioaddr + SDCDIV); + + /* Set the timeout to 500ms */ + writel(host->mmc->clock / 2, host->ioaddr + SDTOUT); +} + +static inline int is_power_of_2(u64 x) +{ + return !(x & (x - 1)); +} + +static int bcm2835_send_cmd(struct udevice *dev, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + struct bcm2835_host *host = dev_get_priv(dev); + u32 edm, fsm; + int r = 0; + + if (data && !is_power_of_2(data->blocksize)) { + printf("unsupported block size (%d bytes)\n", data->blocksize); + + if (cmd) + return -EINVAL; + } + + edm = readl(host->ioaddr + SDEDM); + fsm = edm & SDEDM_FSM_MASK; + + if ((fsm != SDEDM_FSM_IDENTMODE) && + (fsm != SDEDM_FSM_DATAMODE) && + (cmd && cmd->cmdidx != MMC_CMD_STOP_TRANSMISSION)) { + printf("previous command (%d) not complete (EDM %08x)\n", + readl(host->ioaddr + SDCMD) & SDCMD_CMD_MASK, edm); + bcm2835_dumpregs(host); + + if (cmd) + return -EILSEQ; + + return 0; + } + + if (cmd) { + r = bcm2835_send_command(host, cmd, data); + if (!r && !host->use_busy) + r = bcm2835_finish_command(host); + } + + /* Wait for completion of busy signal or data transfer */ + while (host->use_busy || host->data) + bcm2835_irq_poll(host); + + return r; +} + +static int bcm2835_set_ios(struct udevice *dev) +{ + struct bcm2835_host *host = dev_get_priv(dev); + struct mmc *mmc = mmc_get_mmc_dev(dev); + + if (!mmc->clock || mmc->clock != host->clock) { + bcm2835_set_clock(host, mmc->clock); + host->clock = mmc->clock; + } + + /* set bus width */ + host->hcfg &= ~SDHCFG_WIDE_EXT_BUS; + if (mmc->bus_width == 4) + host->hcfg |= SDHCFG_WIDE_EXT_BUS; + + host->hcfg |= SDHCFG_WIDE_INT_BUS; + + /* Disable clever clock switching, to cope with fast core clocks */ + host->hcfg |= SDHCFG_SLOW_CARD; + + writel(host->hcfg, host->ioaddr + SDHCFG); + + return 0; +} + +static int bcm2835_add_host(struct bcm2835_host *host) +{ + struct mmc_config *cfg = &host->plat->cfg; + + cfg->f_max = host->max_clk; + cfg->f_min = host->max_clk / SDCDIV_MAX_CDIV; + cfg->b_max = 65535; + + dev_dbg(dev, "f_max %d, f_min %d\n", + cfg->f_max, cfg->f_min); + + /* host controller capabilities */ + cfg->host_caps = MMC_MODE_4BIT | MMC_MODE_HS | MMC_MODE_HS_52MHz; + + /* report supported voltage ranges */ + cfg->voltages = MMC_VDD_32_33 | MMC_VDD_33_34; + + /* Set interrupt enables */ + host->hcfg = SDHCFG_BUSY_IRPT_EN; + + bcm2835_reset_internal(host); + + return 0; +} + +static int bcm2835_probe(struct udevice *dev) +{ + struct bcm2835_plat *plat = dev_get_platdata(dev); + struct bcm2835_host *host = dev_get_priv(dev); + struct mmc *mmc = mmc_get_mmc_dev(dev); + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + int ret; + + host->dev = dev; + host->mmc = mmc; + host->plat = plat; + upriv->mmc = &plat->mmc; + plat->cfg.name = dev->name; + + host->phys_addr = devfdt_get_addr(dev); + if (host->phys_addr == FDT_ADDR_T_NONE) + return -EINVAL; + + host->ioaddr = devm_ioremap(dev, host->phys_addr, SZ_256); + if (!host->ioaddr) + return -ENOMEM; + + host->max_clk = bcm2835_get_mmc_clock(); + + ret = bcm2835_add_host(host); + if (ret) + goto err; + + dev_dbg(dev, "%s -> OK\n", __func__); + + return 0; + +err: + dev_dbg(dev, "%s -> err %d\n", __func__, ret); + + return ret; +} + +static const struct udevice_id bcm2835_match[] = { + { .compatible = "brcm,bcm2835-sdhost" }, + { } +}; + +static const struct dm_mmc_ops bcm2835_ops = { + .send_cmd = bcm2835_send_cmd, + .set_ios = bcm2835_set_ios, +}; + +static int bcm2835_bind(struct udevice *dev) +{ + struct bcm2835_plat *plat = dev_get_platdata(dev); + + return mmc_bind(dev, &plat->mmc, &plat->cfg); +} + +U_BOOT_DRIVER(bcm2835_sdhost) = { + .name = "bcm2835-sdhost", + .id = UCLASS_MMC, + .of_match = bcm2835_match, + .bind = bcm2835_bind, + .probe = bcm2835_probe, + .priv_auto_alloc_size = sizeof(struct bcm2835_host), + .platdata_auto_alloc_size = sizeof(struct bcm2835_plat), + .ops = &bcm2835_ops, +};