diff options
Diffstat (limited to 'drivers/mmc/host/dw_mmc-socfpga.c')
-rw-r--r-- | drivers/mmc/host/dw_mmc-socfpga.c | 140 |
1 files changed, 140 insertions, 0 deletions
diff --git a/drivers/mmc/host/dw_mmc-socfpga.c b/drivers/mmc/host/dw_mmc-socfpga.c new file mode 100644 index 000000000000..14b5961a851c --- /dev/null +++ b/drivers/mmc/host/dw_mmc-socfpga.c @@ -0,0 +1,140 @@ +/* + * Altera SoCFPGA Specific Extensions for Synopsys DW Multimedia Card Interface + * driver + * + * Copyright (C) 2012, Samsung Electronics Co., Ltd. + * Copyright (C) 2013 Altera Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Taken from dw_mmc-exynos.c + */ +#include <linux/clk.h> +#include <linux/mfd/syscon.h> +#include <linux/mmc/host.h> +#include <linux/mmc/dw_mmc.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +#include "dw_mmc.h" +#include "dw_mmc-pltfm.h" + +#define SYSMGR_SDMMCGRP_CTRL_OFFSET 0x108 +#define DRV_CLK_PHASE_SHIFT_SEL_MASK 0x7 +#define SYSMGR_SDMMC_CTRL_SET(smplsel, drvsel) \ + ((((smplsel) & 0x7) << 3) | (((drvsel) & 0x7) << 0)) + +/* SOCFPGA implementation specific driver private data */ +struct dw_mci_socfpga_priv_data { + u8 ciu_div; /* card interface unit divisor */ + u32 hs_timing; /* bitmask for CIU clock phase shift */ + struct regmap *sysreg; /* regmap for system manager register */ +}; + +static int dw_mci_socfpga_priv_init(struct dw_mci *host) +{ + struct dw_mci_socfpga_priv_data *priv; + + priv = devm_kzalloc(host->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) { + dev_err(host->dev, "mem alloc failed for private data\n"); + return -ENOMEM; + } + + priv->sysreg = syscon_regmap_lookup_by_compatible("altr,sys-mgr"); + if (IS_ERR(priv->sysreg)) { + dev_err(host->dev, "regmap for altr,sys-mgr lookup failed.\n"); + return PTR_ERR(priv->sysreg); + } + host->priv = priv; + + return 0; +} + +static int dw_mci_socfpga_setup_clock(struct dw_mci *host) +{ + struct dw_mci_socfpga_priv_data *priv = host->priv; + + clk_disable_unprepare(host->ciu_clk); + regmap_write(priv->sysreg, SYSMGR_SDMMCGRP_CTRL_OFFSET, + priv->hs_timing); + clk_prepare_enable(host->ciu_clk); + + host->bus_hz /= (priv->ciu_div + 1); + return 0; +} + +static void dw_mci_socfpga_prepare_command(struct dw_mci *host, u32 *cmdr) +{ + struct dw_mci_socfpga_priv_data *priv = host->priv; + + if (priv->hs_timing & DRV_CLK_PHASE_SHIFT_SEL_MASK) + *cmdr |= SDMMC_CMD_USE_HOLD_REG; +} + +static int dw_mci_socfpga_parse_dt(struct dw_mci *host) +{ + struct dw_mci_socfpga_priv_data *priv = host->priv; + struct device_node *np = host->dev->of_node; + u32 timing[2]; + u32 div = 0; + int ret; + + ret = of_property_read_u32(np, "altr,dw-mshc-ciu-div", &div); + if (ret) + dev_info(host->dev, "No dw-mshc-ciu-div specified, assuming 1"); + priv->ciu_div = div; + + ret = of_property_read_u32_array(np, + "altr,dw-mshc-sdr-timing", timing, 2); + if (ret) + return ret; + + priv->hs_timing = SYSMGR_SDMMC_CTRL_SET(timing[0], timing[1]); + return 0; +} + +static const struct dw_mci_drv_data socfpga_drv_data = { + .init = dw_mci_socfpga_priv_init, + .setup_clock = dw_mci_socfpga_setup_clock, + .prepare_command = dw_mci_socfpga_prepare_command, + .parse_dt = dw_mci_socfpga_parse_dt, +}; + +static const struct of_device_id dw_mci_socfpga_match[] = { + { .compatible = "altr,socfpga-dw-mshc", + .data = &socfpga_drv_data, }, + {}, +}; +MODULE_DEVICE_TABLE(of, dw_mci_socfpga_match); + +int dw_mci_socfpga_probe(struct platform_device *pdev) +{ + const struct dw_mci_drv_data *drv_data; + const struct of_device_id *match; + + match = of_match_node(dw_mci_socfpga_match, pdev->dev.of_node); + drv_data = match->data; + return dw_mci_pltfm_register(pdev, drv_data); +} + +static struct platform_driver dw_mci_socfpga_pltfm_driver = { + .probe = dw_mci_socfpga_probe, + .remove = __exit_p(dw_mci_pltfm_remove), + .driver = { + .name = "dwmmc_socfpga", + .of_match_table = of_match_ptr(dw_mci_socfpga_match), + .pm = &dw_mci_pltfm_pmops, + }, +}; + +module_platform_driver(dw_mci_socfpga_pltfm_driver); + +MODULE_DESCRIPTION("Altera SOCFPGA Specific DW-MSHC Driver Extension"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:dwmmc-socfpga"); |