/* * 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) { 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; struct device_node *np = host->dev->of_node; u32 timing[2]; u32 div = 0; int ret; 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); } 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]); host->priv = priv; 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); static 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 = 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");