Message ID | 1454878658-21046-4-git-send-email-mateusz.kulikowski@gmail.com |
---|---|
State | Accepted |
Commit | 9d11d12a16d12caae7f9418322485392a5583423 |
Delegated to: | Tom Rini |
Headers | show |
On 8 February 2016 at 02:27, Mateusz Kulikowski <mateusz.kulikowski@gmail.com> wrote: > Add support for SD/eMMC controller present on some Qualcomm Snapdragon > devices. This controller implements SDHCI 2.0 interface but requires > vendor-specific initialization. > Driver works in PIO mode as ADMA is not supported by U-Boot (yet). > > Signed-off-by: Mateusz Kulikowski <mateusz.kulikowski@gmail.com> > Reviewed-by: Simon Glass <sjg@chromium.org> > Tested-by: Simon Glass <sjg@chromium.org> > --- > > Changes in v3: None > Changes in v2: > - Add reviewed-by > > Changes in v1: > - Added commit message > - Added DT binding documentation > - Added Kconfig help > - Reordered includes > - Dropped redundant fields from msm_sdhc > - Cleaned up clock init code (+ added error handling) > - Dropped mdelay - use wait_for_bit instead in reset code > - Added missing newline after declarations > - Added error handling if "reg" is missing > - Converted base address to pointer > > doc/device-tree-bindings/mmc/msm_sdhci.txt | 25 ++++ > drivers/mmc/Kconfig | 9 ++ > drivers/mmc/Makefile | 1 + > drivers/mmc/msm_sdhci.c | 180 +++++++++++++++++++++++++++++ > 4 files changed, 215 insertions(+) > create mode 100644 doc/device-tree-bindings/mmc/msm_sdhci.txt > create mode 100644 drivers/mmc/msm_sdhci.c > > diff --git a/doc/device-tree-bindings/mmc/msm_sdhci.txt b/doc/device-tree-bindings/mmc/msm_sdhci.txt > new file mode 100644 > index 0000000..08a290c > --- /dev/null > +++ b/doc/device-tree-bindings/mmc/msm_sdhci.txt > @@ -0,0 +1,25 @@ > +Qualcomm Snapdragon SDHCI controller > + > +Required properties: > +- compatible : "qcom,sdhci-msm-v4" > +- reg: Base address and length of registers: > + - Host controller registers (SDHCI) > + - SD Core registers > +- clock: interface clock (must accept SD bus clock as a frequency) > + > +Optional properties: > +- index: If there is more than one controller - controller index (required > + by generic SDHCI code). > +- bus_width: Width of SD/eMMC bus (default 4) > +- clock-frequency: Frequency of SD/eMMC bus (default 400 kHz) > + > +Example: > + > +sdhci@07864000 { > + compatible = "qcom,sdhci-msm-v4"; > + reg = <0x7864900 0x11c 0x7864000 0x800>; > + index = <0x1>; > + bus-width = <0x4>; > + clock = <&clkc 1>; > + clock-frequency = <200000000>; > +}; > diff --git a/drivers/mmc/Kconfig b/drivers/mmc/Kconfig > index 9f4b766..e178935 100644 > --- a/drivers/mmc/Kconfig > +++ b/drivers/mmc/Kconfig > @@ -16,6 +16,15 @@ config DM_MMC > appear as block devices in U-Boot and can support filesystems such > as EXT4 and FAT. > > +config MSM_SDHCI > + bool "Qualcomm SDHCI controller" > + depends on DM_MMC > + help > + Enables support for SDHCI 2.0 controller present on some Qualcomm > + Snapdragon devices. This device is compatible with eMMC v4.5 and > + SD 3.0 specifications. Both SD and eMMC devices are supported. > + Card-detect gpios are not supported. > + > config ROCKCHIP_DWMMC > bool "Rockchip SD/MMC controller support" > depends on DM_MMC && OF_CONTROL > diff --git a/drivers/mmc/Makefile b/drivers/mmc/Makefile > index c9c3e3e..3df92f6 100644 > --- a/drivers/mmc/Makefile > +++ b/drivers/mmc/Makefile > @@ -49,3 +49,4 @@ else > obj-$(CONFIG_GENERIC_MMC) += mmc_write.o > endif > obj-$(CONFIG_PIC32_SDHCI) += pic32_sdhci.o > +obj-$(CONFIG_MSM_SDHCI) += msm_sdhci.o > diff --git a/drivers/mmc/msm_sdhci.c b/drivers/mmc/msm_sdhci.c > new file mode 100644 > index 0000000..1e2a29b > --- /dev/null > +++ b/drivers/mmc/msm_sdhci.c > @@ -0,0 +1,180 @@ > +/* > + * Qualcomm SDHCI driver - SD/eMMC controller > + * > + * (C) Copyright 2015 Mateusz Kulikowski <mateusz.kulikowski@gmail.com> > + * > + * Based on Linux driver > + * > + * SPDX-License-Identifier: GPL-2.0+ > + */ > + > +#include <common.h> > +#include <clk.h> > +#include <dm.h> > +#include <sdhci.h> > +#include <wait_bit.h> > +#include <asm/io.h> > +#include <linux/bitops.h> > + > +/* Non-standard registers needed for SDHCI startup */ > +#define SDCC_MCI_POWER 0x0 > +#define SDCC_MCI_POWER_SW_RST BIT(7) > + > +/* This is undocumented register */ > +#define SDCC_MCI_VERSION 0x50 > +#define SDCC_MCI_VERSION_MAJOR_SHIFT 28 > +#define SDCC_MCI_VERSION_MAJOR_MASK (0xf << SDCC_MCI_VERSION_MAJOR_SHIFT) > +#define SDCC_MCI_VERSION_MINOR_MASK 0xff > + > +#define SDCC_MCI_STATUS2 0x6C > +#define SDCC_MCI_STATUS2_MCI_ACT 0x1 > +#define SDCC_MCI_HC_MODE 0x78 > + > +/* Offset to SDHCI registers */ > +#define SDCC_SDHCI_OFFSET 0x900 > + > +/* Non standard (?) SDHCI register */ > +#define SDHCI_VENDOR_SPEC_CAPABILITIES0 0x11c IMHO, since this is based on Linux please use similar macro's to make easy changes in future. > + > +struct msm_sdhc { > + struct sdhci_host host; > + void *base; > +}; > + > +DECLARE_GLOBAL_DATA_PTR; > + > +static int msm_sdc_clk_init(struct udevice *dev) > +{ > + uint clk_rate = fdtdec_get_uint(gd->fdt_blob, dev->of_offset, > + "clock-frequency", 400000); > + uint clkd[2]; /* clk_id and clk_no */ > + int clk_offset; > + struct udevice *clk; > + int ret; > + > + ret = fdtdec_get_int_array(gd->fdt_blob, dev->of_offset, "clock", clkd, > + 2); > + if (ret) > + return ret; > + > + clk_offset = fdt_node_offset_by_phandle(gd->fdt_blob, clkd[0]); > + if (clk_offset < 0) > + return clk_offset; > + > + ret = uclass_get_device_by_of_offset(UCLASS_CLK, clk_offset, &clk); > + if (ret) > + return ret; > + > + ret = clk_set_periph_rate(clk, clkd[1], clk_rate); > + if (ret < 0) > + return ret; > + > + return 0; > +} > + > +static int msm_sdc_probe(struct udevice *dev) > +{ > + struct msm_sdhc *prv = dev_get_priv(dev); > + struct sdhci_host *host = &prv->host; > + u32 core_version, core_minor, core_major; > + int ret; > + > + host->quirks = SDHCI_QUIRK_WAIT_SEND_CMD | SDHCI_QUIRK_BROKEN_R1B; > + > + /* Init clocks */ > + ret = msm_sdc_clk_init(dev); > + if (ret) > + return ret; > + > + /* Reset the core and Enable SDHC mode */ > + writel(readl(prv->base + SDCC_MCI_POWER) | SDCC_MCI_POWER_SW_RST, > + prv->base + SDCC_MCI_POWER); > + > + > + /* Wait for reset to be written to register */ > + if (wait_for_bit(__func__, prv->base + SDCC_MCI_STATUS2, > + SDCC_MCI_STATUS2_MCI_ACT, false, 10, false)) { > + printf("msm_sdhci: reset request failed\n"); > + return -EIO; > + } > + > + /* SW reset can take upto 10HCLK + 15MCLK cycles. (min 40us) */ > + if (wait_for_bit(__func__, prv->base + SDCC_MCI_POWER, > + SDCC_MCI_POWER_SW_RST, false, 2, false)) { > + printf("msm_sdhci: stuck in reset\n"); > + return -ETIMEDOUT; > + } > + > + /* Enable host-controller mode */ > + writel(1, prv->base + SDCC_MCI_HC_MODE); > + > + core_version = readl(prv->base + SDCC_MCI_VERSION); > + > + core_major = (core_version & SDCC_MCI_VERSION_MAJOR_MASK); > + core_major >>= SDCC_MCI_VERSION_MAJOR_SHIFT; > + > + core_minor = core_version & SDCC_MCI_VERSION_MINOR_MASK; > + > + /* > + * Support for some capabilities is not advertised by newer > + * controller versions and must be explicitly enabled. > + */ > + if (core_major >= 1 && core_minor != 0x11 && core_minor != 0x12) { > + u32 caps = readl(host->ioaddr + SDHCI_CAPABILITIES); > + caps |= SDHCI_CAN_VDD_300 | SDHCI_CAN_DO_8BIT; > + writel(caps, host->ioaddr + SDHCI_VENDOR_SPEC_CAPABILITIES0); > + } > + > + /* Set host controller version */ > + host->version = sdhci_readw(host, SDHCI_HOST_VERSION); > + > + /* automatically detect max and min speed */ > + return add_sdhci(host, 0, 0); > +} > + > +static int msm_sdc_remove(struct udevice *dev) > +{ > + struct msm_sdhc *priv = dev_get_priv(dev); > + > + /* Disable host-controller mode */ > + writel(0, priv->base + SDCC_MCI_HC_MODE); > + > + return 0; > +} > + > +static int msm_ofdata_to_platdata(struct udevice *dev) > +{ > + struct udevice *parent = dev->parent; > + struct msm_sdhc *priv = dev_get_priv(dev); > + struct sdhci_host *host = &priv->host; > + > + host->name = strdup(dev->name); > + host->ioaddr = (void *)dev_get_addr(dev); > + host->bus_width = fdtdec_get_int(gd->fdt_blob, dev->of_offset, > + "bus-width", 4); > + host->index = fdtdec_get_uint(gd->fdt_blob, dev->of_offset, "index", 0); > + priv->base = (void *)fdtdec_get_addr_size_auto_parent(gd->fdt_blob, > + parent->of_offset, > + dev->of_offset, > + "reg", 1, NULL); > + if (priv->base == (void *)FDT_ADDR_T_NONE || > + host->ioaddr == (void *)FDT_ADDR_T_NONE) > + return -EINVAL; > + > + return 0; > +} > + > +static const struct udevice_id msm_mmc_ids[] = { > + { .compatible = "qcom,sdhci-msm-v4" }, > + { } > +}; > + > +U_BOOT_DRIVER(msm_sdc_drv) = { Same as above - msm_sdhci_drv looks more readable and with below driver attributes as well. > + .name = "msm_sdc", > + .id = UCLASS_MMC, > + .of_match = msm_mmc_ids, > + .ofdata_to_platdata = msm_ofdata_to_platdata, > + .probe = msm_sdc_probe, > + .remove = msm_sdc_remove, > + .priv_auto_alloc_size = sizeof(struct msm_sdhc), > +}; > -- > 2.5.0 > thanks!
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 Hi, Ouch - I missed this one - sorry for late reply. On 09.02.2016 22:33, Jagan Teki wrote: > On 8 February 2016 at 02:27, Mateusz Kulikowski > <mateusz.kulikowski@gmail.com> wrote: [...] >> +/* Non-standard registers needed for SDHCI startup */ >> +#define SDCC_MCI_POWER 0x0 >> +#define SDCC_MCI_POWER_SW_RST BIT(7) >> + >> +/* This is undocumented register */ >> +#define SDCC_MCI_VERSION 0x50 >> +#define SDCC_MCI_VERSION_MAJOR_SHIFT 28 >> +#define SDCC_MCI_VERSION_MAJOR_MASK (0xf << SDCC_MCI_VERSION_MAJOR_SHIFT) >> +#define SDCC_MCI_VERSION_MINOR_MASK 0xff >> + >> +#define SDCC_MCI_STATUS2 0x6C >> +#define SDCC_MCI_STATUS2_MCI_ACT 0x1 >> +#define SDCC_MCI_HC_MODE 0x78 >> + >> +/* Offset to SDHCI registers */ >> +#define SDCC_SDHCI_OFFSET 0x900 >> + >> +/* Non standard (?) SDHCI register */ >> +#define SDHCI_VENDOR_SPEC_CAPABILITIES0 0x11c > > IMHO, since this is based on Linux please use similar macro's to make > easy changes in future. I disagree (and I don't like names used on Linux on this particular driver); "My" names are taken from Qualcomm documentation (HRD to be specific) except for VERSION that was named like that for coherency. IMHO it's less confusing and allows faster documentation lookups. Unless Linux names are from some (public) IP Core documentation - then please point me to it and I will do the rename (and add reference to it in the driver). > [...] >> + >> +static const struct udevice_id msm_mmc_ids[] = { >> + { .compatible = "qcom,sdhci-msm-v4" }, >> + { } >> +}; >> + >> +U_BOOT_DRIVER(msm_sdc_drv) = { > > Same as above - msm_sdhci_drv looks more readable and with below > driver attributes as well. Ok, Agree - this will be more coherent with DT bindings / file names. Regards, Mateusz -----BEGIN PGP SIGNATURE----- Version: GnuPG v2 iQEcBAEBCAAGBQJWzjJGAAoJELvtohmVtQzBAlQIAIpXDp744j4xWUQYpq/wNhIO MOmq3HlX+PBgTNPBKm9ub6XCTKa1K5DtGQ/VNKNDx/bv3fYyW7yTAoO4PP1/9eB5 NyM69NYfiQL8euw2y/LGHPqO3jL+T5FG+Y/PSx2GxtFUusGrlMXQG+f+IyhEvY0A 7F5hBK+OixzKlUvm8ZgP4dLzc3dLKI/BaZu7Hz7nzj3BaA2d0MpHUqYISfZU4TB+ mO+iHiFgWsu0yC+KhIxqZmxoVsCWvbVOmEdj37h5IAsxWgRRqQF8UhxTTGZpT7nk U3G3oNPOwTZtsDtfqP2ia3phB2T6ZP6tys8AhNkRuAHEh9d5J7hylkxYwm0eE48= =SXjw -----END PGP SIGNATURE-----
diff --git a/doc/device-tree-bindings/mmc/msm_sdhci.txt b/doc/device-tree-bindings/mmc/msm_sdhci.txt new file mode 100644 index 0000000..08a290c --- /dev/null +++ b/doc/device-tree-bindings/mmc/msm_sdhci.txt @@ -0,0 +1,25 @@ +Qualcomm Snapdragon SDHCI controller + +Required properties: +- compatible : "qcom,sdhci-msm-v4" +- reg: Base address and length of registers: + - Host controller registers (SDHCI) + - SD Core registers +- clock: interface clock (must accept SD bus clock as a frequency) + +Optional properties: +- index: If there is more than one controller - controller index (required + by generic SDHCI code). +- bus_width: Width of SD/eMMC bus (default 4) +- clock-frequency: Frequency of SD/eMMC bus (default 400 kHz) + +Example: + +sdhci@07864000 { + compatible = "qcom,sdhci-msm-v4"; + reg = <0x7864900 0x11c 0x7864000 0x800>; + index = <0x1>; + bus-width = <0x4>; + clock = <&clkc 1>; + clock-frequency = <200000000>; +}; diff --git a/drivers/mmc/Kconfig b/drivers/mmc/Kconfig index 9f4b766..e178935 100644 --- a/drivers/mmc/Kconfig +++ b/drivers/mmc/Kconfig @@ -16,6 +16,15 @@ config DM_MMC appear as block devices in U-Boot and can support filesystems such as EXT4 and FAT. +config MSM_SDHCI + bool "Qualcomm SDHCI controller" + depends on DM_MMC + help + Enables support for SDHCI 2.0 controller present on some Qualcomm + Snapdragon devices. This device is compatible with eMMC v4.5 and + SD 3.0 specifications. Both SD and eMMC devices are supported. + Card-detect gpios are not supported. + config ROCKCHIP_DWMMC bool "Rockchip SD/MMC controller support" depends on DM_MMC && OF_CONTROL diff --git a/drivers/mmc/Makefile b/drivers/mmc/Makefile index c9c3e3e..3df92f6 100644 --- a/drivers/mmc/Makefile +++ b/drivers/mmc/Makefile @@ -49,3 +49,4 @@ else obj-$(CONFIG_GENERIC_MMC) += mmc_write.o endif obj-$(CONFIG_PIC32_SDHCI) += pic32_sdhci.o +obj-$(CONFIG_MSM_SDHCI) += msm_sdhci.o diff --git a/drivers/mmc/msm_sdhci.c b/drivers/mmc/msm_sdhci.c new file mode 100644 index 0000000..1e2a29b --- /dev/null +++ b/drivers/mmc/msm_sdhci.c @@ -0,0 +1,180 @@ +/* + * Qualcomm SDHCI driver - SD/eMMC controller + * + * (C) Copyright 2015 Mateusz Kulikowski <mateusz.kulikowski@gmail.com> + * + * Based on Linux driver + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <clk.h> +#include <dm.h> +#include <sdhci.h> +#include <wait_bit.h> +#include <asm/io.h> +#include <linux/bitops.h> + +/* Non-standard registers needed for SDHCI startup */ +#define SDCC_MCI_POWER 0x0 +#define SDCC_MCI_POWER_SW_RST BIT(7) + +/* This is undocumented register */ +#define SDCC_MCI_VERSION 0x50 +#define SDCC_MCI_VERSION_MAJOR_SHIFT 28 +#define SDCC_MCI_VERSION_MAJOR_MASK (0xf << SDCC_MCI_VERSION_MAJOR_SHIFT) +#define SDCC_MCI_VERSION_MINOR_MASK 0xff + +#define SDCC_MCI_STATUS2 0x6C +#define SDCC_MCI_STATUS2_MCI_ACT 0x1 +#define SDCC_MCI_HC_MODE 0x78 + +/* Offset to SDHCI registers */ +#define SDCC_SDHCI_OFFSET 0x900 + +/* Non standard (?) SDHCI register */ +#define SDHCI_VENDOR_SPEC_CAPABILITIES0 0x11c + +struct msm_sdhc { + struct sdhci_host host; + void *base; +}; + +DECLARE_GLOBAL_DATA_PTR; + +static int msm_sdc_clk_init(struct udevice *dev) +{ + uint clk_rate = fdtdec_get_uint(gd->fdt_blob, dev->of_offset, + "clock-frequency", 400000); + uint clkd[2]; /* clk_id and clk_no */ + int clk_offset; + struct udevice *clk; + int ret; + + ret = fdtdec_get_int_array(gd->fdt_blob, dev->of_offset, "clock", clkd, + 2); + if (ret) + return ret; + + clk_offset = fdt_node_offset_by_phandle(gd->fdt_blob, clkd[0]); + if (clk_offset < 0) + return clk_offset; + + ret = uclass_get_device_by_of_offset(UCLASS_CLK, clk_offset, &clk); + if (ret) + return ret; + + ret = clk_set_periph_rate(clk, clkd[1], clk_rate); + if (ret < 0) + return ret; + + return 0; +} + +static int msm_sdc_probe(struct udevice *dev) +{ + struct msm_sdhc *prv = dev_get_priv(dev); + struct sdhci_host *host = &prv->host; + u32 core_version, core_minor, core_major; + int ret; + + host->quirks = SDHCI_QUIRK_WAIT_SEND_CMD | SDHCI_QUIRK_BROKEN_R1B; + + /* Init clocks */ + ret = msm_sdc_clk_init(dev); + if (ret) + return ret; + + /* Reset the core and Enable SDHC mode */ + writel(readl(prv->base + SDCC_MCI_POWER) | SDCC_MCI_POWER_SW_RST, + prv->base + SDCC_MCI_POWER); + + + /* Wait for reset to be written to register */ + if (wait_for_bit(__func__, prv->base + SDCC_MCI_STATUS2, + SDCC_MCI_STATUS2_MCI_ACT, false, 10, false)) { + printf("msm_sdhci: reset request failed\n"); + return -EIO; + } + + /* SW reset can take upto 10HCLK + 15MCLK cycles. (min 40us) */ + if (wait_for_bit(__func__, prv->base + SDCC_MCI_POWER, + SDCC_MCI_POWER_SW_RST, false, 2, false)) { + printf("msm_sdhci: stuck in reset\n"); + return -ETIMEDOUT; + } + + /* Enable host-controller mode */ + writel(1, prv->base + SDCC_MCI_HC_MODE); + + core_version = readl(prv->base + SDCC_MCI_VERSION); + + core_major = (core_version & SDCC_MCI_VERSION_MAJOR_MASK); + core_major >>= SDCC_MCI_VERSION_MAJOR_SHIFT; + + core_minor = core_version & SDCC_MCI_VERSION_MINOR_MASK; + + /* + * Support for some capabilities is not advertised by newer + * controller versions and must be explicitly enabled. + */ + if (core_major >= 1 && core_minor != 0x11 && core_minor != 0x12) { + u32 caps = readl(host->ioaddr + SDHCI_CAPABILITIES); + caps |= SDHCI_CAN_VDD_300 | SDHCI_CAN_DO_8BIT; + writel(caps, host->ioaddr + SDHCI_VENDOR_SPEC_CAPABILITIES0); + } + + /* Set host controller version */ + host->version = sdhci_readw(host, SDHCI_HOST_VERSION); + + /* automatically detect max and min speed */ + return add_sdhci(host, 0, 0); +} + +static int msm_sdc_remove(struct udevice *dev) +{ + struct msm_sdhc *priv = dev_get_priv(dev); + + /* Disable host-controller mode */ + writel(0, priv->base + SDCC_MCI_HC_MODE); + + return 0; +} + +static int msm_ofdata_to_platdata(struct udevice *dev) +{ + struct udevice *parent = dev->parent; + struct msm_sdhc *priv = dev_get_priv(dev); + struct sdhci_host *host = &priv->host; + + host->name = strdup(dev->name); + host->ioaddr = (void *)dev_get_addr(dev); + host->bus_width = fdtdec_get_int(gd->fdt_blob, dev->of_offset, + "bus-width", 4); + host->index = fdtdec_get_uint(gd->fdt_blob, dev->of_offset, "index", 0); + priv->base = (void *)fdtdec_get_addr_size_auto_parent(gd->fdt_blob, + parent->of_offset, + dev->of_offset, + "reg", 1, NULL); + if (priv->base == (void *)FDT_ADDR_T_NONE || + host->ioaddr == (void *)FDT_ADDR_T_NONE) + return -EINVAL; + + return 0; +} + +static const struct udevice_id msm_mmc_ids[] = { + { .compatible = "qcom,sdhci-msm-v4" }, + { } +}; + +U_BOOT_DRIVER(msm_sdc_drv) = { + .name = "msm_sdc", + .id = UCLASS_MMC, + .of_match = msm_mmc_ids, + .ofdata_to_platdata = msm_ofdata_to_platdata, + .probe = msm_sdc_probe, + .remove = msm_sdc_remove, + .priv_auto_alloc_size = sizeof(struct msm_sdhc), +};