diff options
author | Philipp Tomsich <philipp.tomsich@theobroma-systems.com> | 2017-02-10 13:41:26 +0100 |
---|---|---|
committer | Philipp Tomsich <philipp.tomsich@theobroma-systems.com> | 2017-03-09 01:40:20 +0100 |
commit | 62b8c218905dca1ce530de4d4a91edfaf46c1eb7 (patch) | |
tree | b068bd0883b515230d2085caa22be1da14816b40 | |
parent | 7d773ef40e7e98fd45d31ce1842460045d0ca3f9 (diff) |
sunxi_mmc: convert to a device-model driver
We now support the device-model for configuration of the driver
including the interface to the pinctrl, reset and clock frameworks.
Signed-off-by: Philipp Tomsich <philipp.tomsich@theobroma-systems.com>
-rw-r--r-- | board/sunxi/board.c | 7 | ||||
-rw-r--r-- | drivers/mmc/sunxi_mmc.c | 350 |
2 files changed, 331 insertions, 26 deletions
diff --git a/board/sunxi/board.c b/board/sunxi/board.c index 93a9785633..a6373bd50c 100644 --- a/board/sunxi/board.c +++ b/board/sunxi/board.c @@ -246,7 +246,7 @@ void board_nand_init(void) } #endif -#ifdef CONFIG_GENERIC_MMC +#if defined(CONFIG_GENERIC_MMC) && !(defined(CONFIG_DM_MMC) && defined(CONFIG_PINCTRL)) static void mmc_pinmux_setup(int sdc) { unsigned int pin; @@ -424,6 +424,7 @@ static void mmc_pinmux_setup(int sdc) int board_mmc_init(bd_t *bis) { +#if !(defined(CONFIG_DM_MMC) && defined(CONFIG_PINCTRL)) __maybe_unused struct mmc *mmc0, *mmc1; __maybe_unused char buf[512]; __maybe_unused u32 val; @@ -491,7 +492,7 @@ int board_mmc_init(bd_t *bis) } #endif #endif - +#endif return 0; } #endif @@ -856,6 +857,7 @@ static void setup_environment(const void *fdt) */ static void setup_environment(const void *fdt) { +#if !defined(CONFIG_DM_MMC) uint8_t mac_addr[6]; char serial_string[17] = { 0 }; struct mmc *mmc0; @@ -893,6 +895,7 @@ static void setup_environment(const void *fdt) mmc0->cid[0]>>24,mmc0->cid[2]&0xff,mmc0->cid[3]>>8); setenv("serial#", serial_string); } +#endif } #endif diff --git a/drivers/mmc/sunxi_mmc.c b/drivers/mmc/sunxi_mmc.c index 46abe4a4e2..8075b9ccf1 100644 --- a/drivers/mmc/sunxi_mmc.c +++ b/drivers/mmc/sunxi_mmc.c @@ -3,30 +3,65 @@ * Allwinner Technology Co., Ltd. <www.allwinnertech.com> * Aaron <leafy.myeh@allwinnertech.com> * + * (C) 2017 Theobroma Systems Design und Consulting GmbH + * * MMC driver for allwinner sunxi platform. * * SPDX-License-Identifier: GPL-2.0+ */ #include <common.h> -#include <errno.h> -#include <malloc.h> -#include <mmc.h> -#include <asm/io.h> +#include <asm-generic/gpio.h> #include <asm/arch/clock.h> #include <asm/arch/cpu.h> #include <asm/arch/gpio.h> #include <asm/arch/mmc.h> -#include <asm-generic/gpio.h> +#include <asm/io.h> +#include <clk.h> +#include <dm/device.h> +#include <dt-structs.h> +#include <errno.h> +#include <linux/iopoll.h> +#include <malloc.h> +#include <mmc.h> +#include <reset.h> + +DECLARE_GLOBAL_DATA_PTR; + +struct sunxi_mmc_plat { + struct mmc mmc; +}; struct sunxi_mmc_host { unsigned mmc_no; +#if !defined(CONFIG_DM_MMC) uint32_t *mclkreg; +#endif unsigned fatal_err; struct sunxi_mmc *reg; struct mmc_config cfg; + bool cd_inverted; +#if defined(CONFIG_DM_MMC) + struct mmc *mmc; + struct gpio_desc cd_gpio; /* card-detect (optional) */ + struct gpio_desc pwr_gpio; /* power-enabled (optional) */ + struct gpio_desc wp_gpio; /* write-protect (optional) */ + bool wp_inverted; + struct reset_ctl reset; + struct clk ahb_clk_gate; + struct clk mmc_clk; +#else + int cd_pin; +#endif }; +#if defined(CONFIG_DM_MMC) && defined(CONFIG_DM_MMC_OPS) +static const struct dm_mmc_ops sunxi_mmc_ops; +#else +static const struct mmc_ops sunxi_mmc_ops; +#endif + +#if !defined(CONFIG_DM_MMC) /* support 4 mmc hosts */ struct sunxi_mmc_host mmc_host[4]; @@ -36,7 +71,9 @@ static int sunxi_mmc_getcd_gpio(int sdc_no) case 0: return sunxi_name_to_gpio(CONFIG_MMC0_CD_PIN); case 1: return sunxi_name_to_gpio(CONFIG_MMC1_CD_PIN); case 2: return sunxi_name_to_gpio(CONFIG_MMC2_CD_PIN); +#if !defined(CONFIG_ARCH_SUN50I) /* only 3 MMC controllers on the A64 */ case 3: return sunxi_name_to_gpio(CONFIG_MMC3_CD_PIN); +#endif } return -EINVAL; } @@ -62,10 +99,12 @@ static int mmc_resource_init(int sdc_no) mmchost->reg = (struct sunxi_mmc *)SUNXI_MMC2_BASE; mmchost->mclkreg = &ccm->sd2_clk_cfg; break; +#if !defined(CONFIG_ARCH_SUN50I) /* only 3 MMC controllers on the A64 */ case 3: mmchost->reg = (struct sunxi_mmc *)SUNXI_MMC3_BASE; mmchost->mclkreg = &ccm->sd3_clk_cfg; break; +#endif default: printf("Wrong mmc number %d\n", sdc_no); return -1; @@ -80,10 +119,47 @@ static int mmc_resource_init(int sdc_no) ret = gpio_direction_input(cd_pin); } } + mmchost->cd_pin = cd_pin; + + return ret; +} +#endif + +#if defined(CONFIG_DM_MMC) +static int mmc_resource_init_from_udev(struct udevice *dev) +{ + struct sunxi_mmc_host *mmchost = dev_get_priv(dev); + int ret = 0; + + debug("%s: %s\n", dev->name, __func__); + + switch ((uintptr_t)mmchost->reg) { + case SUNXI_MMC0_BASE: + mmchost->mmc_no = 0; + break; + case SUNXI_MMC1_BASE: + mmchost->mmc_no = 1; + break; + case SUNXI_MMC2_BASE: + mmchost->mmc_no = 2; + break; +#if !defined(CONFIG_ARCH_SUN50I) /* only 3 MMC controllers on the A64 */ + case SUNXI_MMC3_BASE: + mmchost->mmc_no = 3; + break; +#endif + default: + debug("%s: unknown base address %p\n", __func__, mmchost->reg); + return -1; + } + + debug("%s: mmc_no %d\n", dev->name, mmchost->mmc_no); return ret; } +#endif +#if !defined(CONFIG_DM_MMC) static int mmc_set_mod_clk(struct sunxi_mmc_host *mmchost, unsigned int hz) { unsigned int pll, pll_hz, div, n, oclk_dly, sclk_dly; @@ -154,10 +230,10 @@ static int mmc_set_mod_clk(struct sunxi_mmc_host *mmchost, unsigned int hz) return 0; } -static int mmc_clk_io_on(int sdc_no) +static int mmc_clk_io_on(struct sunxi_mmc_host *mmchost) { - struct sunxi_mmc_host *mmchost = &mmc_host[sdc_no]; struct sunxi_ccm_reg *ccm = (struct sunxi_ccm_reg *)SUNXI_CCM_BASE; + int sdc_no = mmchost->mmc_no; debug("init mmc %d clock and io\n", sdc_no); @@ -176,6 +252,30 @@ static int mmc_clk_io_on(int sdc_no) return mmc_set_mod_clk(mmchost, 24000000); } +#endif + +#if defined(CONFIG_DM_MMC) +static int mmc_clk_io_on(struct sunxi_mmc_host *mmchost) +{ + /* Enable the AHB clock gate */ + clk_enable(&mmchost->ahb_clk_gate); + + /* Deassert the AHB module reset */ + reset_deassert(&mmchost->reset); + +#if defined(CONFIG_MACH_SUN9I) + /* TODO --- covert this to DM */ + /* sun9i has a mmc-common module, also set the gate and reset there */ + writel(SUNXI_MMC_COMMON_CLK_GATE | SUNXI_MMC_COMMON_RESET, + SUNXI_MMC_COMMON_BASE + 4 * sdc_no); +#endif + + clk_set_rate(&mmchost->mmc_clk, 24000000); + clk_enable(&mmchost->mmc_clk); + + return 0; +} +#endif static int mmc_update_clk(struct mmc *mmc) { @@ -184,6 +284,8 @@ static int mmc_update_clk(struct mmc *mmc) unsigned timeout_msecs = 2000; unsigned long start = get_timer(0); + debug("%s: base %p\n", __func__, mmchost->reg); + cmd = SUNXI_MMC_CMD_START | SUNXI_MMC_CMD_UPCLK_ONLY | SUNXI_MMC_CMD_WAIT_PRE_OVER; @@ -210,9 +312,14 @@ static int mmc_config_clock(struct mmc *mmc) if (mmc_update_clk(mmc)) return -1; +#if !defined(CONFIG_DM_MMC) /* Set mod_clk to new rate */ if (mmc_set_mod_clk(mmchost, mmc->clock)) return -1; +#else + if (clk_set_rate(&mmchost->mmc_clk, mmc->clock) == 0) + return -1; +#endif /* Clear internal divider */ rval &= ~SUNXI_MMC_CLK_DIVIDER_MASK; @@ -227,8 +334,14 @@ static int mmc_config_clock(struct mmc *mmc) return 0; } +#if defined(CONFIG_DM_MMC_OPS) +static int sunxi_mmc_set_ios(struct udevice *dev) +{ + struct mmc *mmc = mmc_get_mmc_dev(dev); +#else static int sunxi_mmc_set_ios(struct mmc *mmc) { +#endif struct sunxi_mmc_host *mmchost = mmc->priv; debug("set ios: bus_width: %x, clock: %d\n", @@ -254,12 +367,19 @@ static int sunxi_mmc_set_ios(struct mmc *mmc) static int sunxi_mmc_core_init(struct mmc *mmc) { struct sunxi_mmc_host *mmchost = mmc->priv; + uint32_t regval; + int ret = 0; + + debug("%s: base %p", __func__, mmchost->reg); /* Reset controller */ writel(SUNXI_MMC_GCTRL_RESET, &mmchost->reg->gctrl); - udelay(1000); - return 0; + /* Wait for the reset bit (auto-clearing) to deassert */ + ret = readl_poll_timeout(&mmchost->reg->gctrl, regval, + !(regval & SUNXI_MMC_GCTRL_RESET), 1000000); + + return ret; } static int mmc_trans_data_by_cpu(struct mmc *mmc, struct mmc_data *data) @@ -317,9 +437,16 @@ static int mmc_rint_wait(struct mmc *mmc, unsigned int timeout_msecs, return 0; } +#if defined(CONFIG_DM_MMC_OPS) +static int sunxi_mmc_send_cmd(struct udevice *dev, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + struct mmc *mmc = mmc_get_mmc_dev(dev); +#else static int sunxi_mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, struct mmc_data *data) { +#endif struct sunxi_mmc_host *mmchost = mmc->priv; unsigned int cmdval = SUNXI_MMC_CMD_START; unsigned int timeout_msecs; @@ -358,7 +485,7 @@ static int sunxi_mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, writel(data->blocks * data->blocksize, &mmchost->reg->bytecnt); } - debug("mmc %d, cmd %d(0x%08x), arg 0x%08x\n", mmchost->mmc_no, + debug("mmc %p, cmd %d(0x%08x), arg 0x%08x\n", mmchost, cmd->cmdidx, cmdval | cmd->cmdidx, cmd->cmdarg); writel(cmd->cmdarg, &mmchost->reg->arg); @@ -439,30 +566,52 @@ out: return error; } +static inline int cdpin_is_valid(struct sunxi_mmc_host *priv) +{ +#if !defined(CONFIG_DM_MMC) + return priv->cd_pin >= 0; +#else + return dm_gpio_is_valid(&priv->cd_gpio); +#endif +} + +static inline int cdpin_get_value(struct sunxi_mmc_host *priv) +{ +#if !defined(CONFIG_DM_MMC) + return gpio_get_value(priv->cd_pin); +#else + return dm_gpio_get_value(&priv->cd_gpio); +#endif +} + +#if defined(CONFIG_DM_MMC_OPS) +static int sunxi_mmc_getcd(struct udevice *dev) +{ + struct mmc *mmc = mmc_get_mmc_dev(dev); +#else static int sunxi_mmc_getcd(struct mmc *mmc) { - struct sunxi_mmc_host *mmchost = mmc->priv; - int cd_pin; +#endif + struct sunxi_mmc_host *priv = mmc->priv; + int value = 1; - cd_pin = sunxi_mmc_getcd_gpio(mmchost->mmc_no); - if (cd_pin < 0) - return 1; + if (cdpin_is_valid(priv)) { + value = cdpin_get_value(priv); - return !gpio_get_value(cd_pin); -} + if (priv->cd_inverted) + return !value; + } -static const struct mmc_ops sunxi_mmc_ops = { - .send_cmd = sunxi_mmc_send_cmd, - .set_ios = sunxi_mmc_set_ios, - .init = sunxi_mmc_core_init, - .getcd = sunxi_mmc_getcd, -}; + return value; +} +#if !defined(CONFIG_DM_MMC) struct mmc *sunxi_mmc_init(int sdc_no) { struct mmc_config *cfg = &mmc_host[sdc_no].cfg; memset(&mmc_host[sdc_no], 0, sizeof(struct sunxi_mmc_host)); + mmc_host[sdc_no].cd_inverted = true; cfg->name = "SUNXI SD/MMC"; cfg->ops = &sunxi_mmc_ops; @@ -482,7 +631,160 @@ struct mmc *sunxi_mmc_init(int sdc_no) if (mmc_resource_init(sdc_no) != 0) return NULL; - mmc_clk_io_on(sdc_no); + mmc_clk_io_on(&mmc_host[sdc_no]); return mmc_create(cfg, &mmc_host[sdc_no]); } +#endif + +#if defined(CONFIG_DM_MMC) && defined(CONFIG_DM_MMC_OPS) +static const struct dm_mmc_ops sunxi_mmc_ops = { + .send_cmd = sunxi_mmc_send_cmd, + .set_ios = sunxi_mmc_set_ios, + .get_cd = sunxi_mmc_getcd, +}; +#else +static const struct mmc_ops sunxi_mmc_ops = { + .send_cmd = sunxi_mmc_send_cmd, + .set_ios = sunxi_mmc_set_ios, + .init = sunxi_mmc_core_init, + .getcd = sunxi_mmc_getcd, +}; +#endif + +#if defined(CONFIG_DM_MMC) +static int sunxi_mmc_ofdata_to_platdata(struct udevice *dev) +{ + return 0; +} + +static int sunxi_mmc_probe(struct udevice *dev) +{ + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); +#if defined(CONFIG_BLK) + struct sunxi_mmc_plat *plat = dev_get_platdata(dev); +#endif + struct sunxi_mmc_host *priv = dev_get_priv(dev); + struct mmc_config *cfg = &priv->cfg; + int bus_width; + u32 f_minmax[2]; + + priv->reg = (void *)dev_get_addr(dev); + cfg->name = "SUNXI SD/MMC"; +#if !(defined(CONFIG_DM_MMC) && defined(CONFIG_DM_MMC_OPS)) + cfg->ops = &sunxi_mmc_ops; +#endif + cfg->voltages = MMC_VDD_32_33 | MMC_VDD_33_34; + cfg->b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT; + cfg->host_caps = MMC_MODE_HS_52MHz | MMC_MODE_HS; + + bus_width = fdtdec_get_int(gd->fdt_blob, dev->of_offset, + "bus-width", 4); + if (bus_width == 8) + cfg->host_caps |= MMC_MODE_8BIT; + else if (bus_width == 4) + cfg->host_caps |= MMC_MODE_4BIT; + + debug("%s: reg %p bus_width %d\n", dev->name, priv->reg, bus_width); + + if (!fdtdec_get_int_array(gd->fdt_blob, dev->of_offset, + "clock-freq-min-max", f_minmax, 2)) { + cfg->f_min = f_minmax[0]; + cfg->f_max = f_minmax[1]; + } else { + /* use the defaults */ + cfg->f_min = 400000; + cfg->f_max = 52000000; + } + + /* Some legacy functionality in our tree still depends on the + * mmchost->mmc_no... until we can get rid of this, initialise + * it based on the base address of the device. + */ + if (mmc_resource_init_from_udev(dev) != 0) + return -EINVAL; + + /* All GPIOs are optional */ + gpio_request_by_name(dev, "cd-gpios", 0, + &priv->cd_gpio, GPIOD_IS_IN); + priv->cd_inverted = fdtdec_get_bool(gd->fdt_blob, dev->of_offset, + "cd-inverted"); + gpio_request_by_name(dev, "wp-gpios", 0, + &priv->wp_gpio, GPIOD_IS_IN); + priv->wp_inverted = fdtdec_get_bool(gd->fdt_blob, dev->of_offset, + "wp-inverted"); + gpio_request_by_name(dev, "power-gpios", 0, + &priv->pwr_gpio, GPIOD_IS_OUT); + if (dm_gpio_is_valid(&priv->pwr_gpio)) + dm_gpio_set_value(&priv->pwr_gpio, 1); + + if (reset_get_by_name(dev, "ahb", &priv->reset)) { + error("%s: failed to get 'ahb' reset\n", dev->name); + return -EINVAL; + } + + if (clk_get_by_name(dev, "ahb", &priv->ahb_clk_gate) || + clk_get_by_name(dev, "mmc", &priv->mmc_clk)) { + error("%s: failed to get all required clocks ('ahb', 'mmc')\n", + dev->name); + return -EINVAL; + } + + mmc_clk_io_on(priv); + +#if defined(CONFIG_BLK) + priv->mmc = &plat->mmc; +#else + priv->mmc = mmc_create(cfg, priv); + if (priv->mmc == NULL) + return -1; +#endif + priv->mmc->priv = priv; + priv->mmc->dev = dev; + priv->mmc->cfg = cfg; + priv->mmc->has_init = 0; + upriv->mmc = priv->mmc; + +#if defined(CONFIG_DM_MMC) && defined(CONFIG_DM_MMC_OPS) + sunxi_mmc_core_init(priv->mmc); +#endif + return 0; +} + +#if defined(CONFIG_BLK) +static int sunxi_mmc_bind(struct udevice *dev) +{ + struct sunxi_mmc_plat *plat = dev_get_platdata(dev); + struct sunxi_mmc_host *priv = dev_get_priv(dev); + + debug("%s: %s\n", dev->name, __func__); + + /* TODO: To move cfg into plat, we need to change the legacy + code, which references through the arrays... */ + + return mmc_bind(dev, &plat->mmc, &priv->cfg); +} +#endif + +static const struct udevice_id sunxi_mmc_ids[] = { + { .compatible = "allwinner,sun50i-a64-mmc" }, + { } +}; + +U_BOOT_DRIVER(sunxi_mmc_drv) = { + .name = "sunxi_mmc", + .id = UCLASS_MMC, + .of_match = sunxi_mmc_ids, + .ofdata_to_platdata = sunxi_mmc_ofdata_to_platdata, + .probe = sunxi_mmc_probe, + .priv_auto_alloc_size = sizeof(struct sunxi_mmc_host), + .platdata_auto_alloc_size = sizeof(struct sunxi_mmc_plat), +#if defined(CONFIG_DM_MMC_OPS) + .ops = &sunxi_mmc_ops, +#endif +#if defined(CONFIG_BLK) + .bind = sunxi_mmc_bind, +#endif +}; + +#endif |