| /* |
| * 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"); |