162ca8034SShashidhar Hiremath /* 262ca8034SShashidhar Hiremath * Synopsys DesignWare Multimedia Card Interface driver 362ca8034SShashidhar Hiremath * 462ca8034SShashidhar Hiremath * Copyright (C) 2009 NXP Semiconductors 562ca8034SShashidhar Hiremath * Copyright (C) 2009, 2010 Imagination Technologies Ltd. 662ca8034SShashidhar Hiremath * 762ca8034SShashidhar Hiremath * This program is free software; you can redistribute it and/or modify 862ca8034SShashidhar Hiremath * it under the terms of the GNU General Public License as published by 962ca8034SShashidhar Hiremath * the Free Software Foundation; either version 2 of the License, or 1062ca8034SShashidhar Hiremath * (at your option) any later version. 1162ca8034SShashidhar Hiremath */ 1262ca8034SShashidhar Hiremath 13a3e2cd7fSThierry Reding #include <linux/err.h> 1462ca8034SShashidhar Hiremath #include <linux/interrupt.h> 1562ca8034SShashidhar Hiremath #include <linux/module.h> 1662ca8034SShashidhar Hiremath #include <linux/io.h> 1762ca8034SShashidhar Hiremath #include <linux/irq.h> 1862ca8034SShashidhar Hiremath #include <linux/platform_device.h> 1962ca8034SShashidhar Hiremath #include <linux/slab.h> 2062ca8034SShashidhar Hiremath #include <linux/mmc/host.h> 2162ca8034SShashidhar Hiremath #include <linux/mmc/mmc.h> 2262ca8034SShashidhar Hiremath #include <linux/mmc/dw_mmc.h> 23c91eab4bSThomas Abraham #include <linux/of.h> 24f629ba2cSAddy Ke #include <linux/clk.h> 25c91eab4bSThomas Abraham 2662ca8034SShashidhar Hiremath #include "dw_mmc.h" 277725a52cSJingoo Han #include "dw_mmc-pltfm.h" 2862ca8034SShashidhar Hiremath 29f629ba2cSAddy Ke #define RK3288_CLKGEN_DIV 2 30f629ba2cSAddy Ke 31ec1e5d70SDinh Nguyen static void dw_mci_pltfm_prepare_command(struct dw_mci *host, u32 *cmdr) 32c73e41c8SHeiko Stübner { 33c73e41c8SHeiko Stübner *cmdr |= SDMMC_CMD_USE_HOLD_REG; 34c73e41c8SHeiko Stübner } 35c73e41c8SHeiko Stübner 36f629ba2cSAddy Ke static int dw_mci_rk3288_setup_clock(struct dw_mci *host) 37f629ba2cSAddy Ke { 38f629ba2cSAddy Ke host->bus_hz /= RK3288_CLKGEN_DIV; 39f629ba2cSAddy Ke 40f629ba2cSAddy Ke return 0; 41f629ba2cSAddy Ke } 42f629ba2cSAddy Ke 43f629ba2cSAddy Ke static void dw_mci_rk3288_set_ios(struct dw_mci *host, struct mmc_ios *ios) 44f629ba2cSAddy Ke { 45f629ba2cSAddy Ke int ret; 46f629ba2cSAddy Ke unsigned int cclkin; 47f629ba2cSAddy Ke u32 bus_hz; 48f629ba2cSAddy Ke 49f629ba2cSAddy Ke /* 50f629ba2cSAddy Ke * cclkin: source clock of mmc controller. 51f629ba2cSAddy Ke * bus_hz: card interface clock generated by CLKGEN. 52f629ba2cSAddy Ke * bus_hz = cclkin / RK3288_CLKGEN_DIV; 53f629ba2cSAddy Ke * ios->clock = (div == 0) ? bus_hz : (bus_hz / (2 * div)) 54f629ba2cSAddy Ke * 55f629ba2cSAddy Ke * Note: div can only be 0 or 1 56f629ba2cSAddy Ke * if DDR50 8bit mode(only emmc work in 8bit mode), 57f629ba2cSAddy Ke * div must be set 1 58f629ba2cSAddy Ke */ 59f629ba2cSAddy Ke if ((ios->bus_width == MMC_BUS_WIDTH_8) && 60f629ba2cSAddy Ke (ios->timing == MMC_TIMING_MMC_DDR52)) 61f629ba2cSAddy Ke cclkin = 2 * ios->clock * RK3288_CLKGEN_DIV; 62f629ba2cSAddy Ke else 63f629ba2cSAddy Ke cclkin = ios->clock * RK3288_CLKGEN_DIV; 64f629ba2cSAddy Ke 65f629ba2cSAddy Ke ret = clk_set_rate(host->ciu_clk, cclkin); 66f629ba2cSAddy Ke if (ret) 67f629ba2cSAddy Ke dev_warn(host->dev, "failed to set rate %uHz\n", ios->clock); 68f629ba2cSAddy Ke 69f629ba2cSAddy Ke bus_hz = clk_get_rate(host->ciu_clk) / RK3288_CLKGEN_DIV; 70f629ba2cSAddy Ke if (bus_hz != host->bus_hz) { 71f629ba2cSAddy Ke host->bus_hz = bus_hz; 72f629ba2cSAddy Ke /* force dw_mci_setup_bus() */ 73f629ba2cSAddy Ke host->current_speed = 0; 74f629ba2cSAddy Ke } 75f629ba2cSAddy Ke } 76f629ba2cSAddy Ke 77f629ba2cSAddy Ke static const struct dw_mci_drv_data rk2928_drv_data = { 78ec1e5d70SDinh Nguyen .prepare_command = dw_mci_pltfm_prepare_command, 79ec1e5d70SDinh Nguyen }; 80ec1e5d70SDinh Nguyen 81f629ba2cSAddy Ke static const struct dw_mci_drv_data rk3288_drv_data = { 82f629ba2cSAddy Ke .prepare_command = dw_mci_pltfm_prepare_command, 83f629ba2cSAddy Ke .set_ios = dw_mci_rk3288_set_ios, 84f629ba2cSAddy Ke .setup_clock = dw_mci_rk3288_setup_clock, 85f629ba2cSAddy Ke }; 86f629ba2cSAddy Ke 87ec1e5d70SDinh Nguyen static const struct dw_mci_drv_data socfpga_drv_data = { 88ec1e5d70SDinh Nguyen .prepare_command = dw_mci_pltfm_prepare_command, 89c73e41c8SHeiko Stübner }; 90c73e41c8SHeiko Stübner 91800d78bfSThomas Abraham int dw_mci_pltfm_register(struct platform_device *pdev, 928e2b36eaSArnd Bergmann const struct dw_mci_drv_data *drv_data) 9362ca8034SShashidhar Hiremath { 9462ca8034SShashidhar Hiremath struct dw_mci *host; 9562ca8034SShashidhar Hiremath struct resource *regs; 9662ca8034SShashidhar Hiremath 97bb8bdc77SThomas Abraham host = devm_kzalloc(&pdev->dev, sizeof(struct dw_mci), GFP_KERNEL); 9862ca8034SShashidhar Hiremath if (!host) 9962ca8034SShashidhar Hiremath return -ENOMEM; 10062ca8034SShashidhar Hiremath 10162ca8034SShashidhar Hiremath host->irq = platform_get_irq(pdev, 0); 102bb8bdc77SThomas Abraham if (host->irq < 0) 103bb8bdc77SThomas Abraham return host->irq; 10462ca8034SShashidhar Hiremath 105800d78bfSThomas Abraham host->drv_data = drv_data; 1064a90920cSThomas Abraham host->dev = &pdev->dev; 10762ca8034SShashidhar Hiremath host->irq_flags = 0; 10862ca8034SShashidhar Hiremath host->pdata = pdev->dev.platform_data; 109bcc87666SAndy Shevchenko 110bcc87666SAndy Shevchenko regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); 111a3e2cd7fSThierry Reding host->regs = devm_ioremap_resource(&pdev->dev, regs); 112a3e2cd7fSThierry Reding if (IS_ERR(host->regs)) 113a3e2cd7fSThierry Reding return PTR_ERR(host->regs); 114bb8bdc77SThomas Abraham 11562ca8034SShashidhar Hiremath platform_set_drvdata(pdev, host); 116dd369800SAndy Shevchenko return dw_mci_probe(host); 11762ca8034SShashidhar Hiremath } 11817403f23SThomas Abraham EXPORT_SYMBOL_GPL(dw_mci_pltfm_register); 11917403f23SThomas Abraham 12062ca8034SShashidhar Hiremath #ifdef CONFIG_PM_SLEEP 12162ca8034SShashidhar Hiremath /* 12262ca8034SShashidhar Hiremath * TODO: we should probably disable the clock to the card in the suspend path. 12362ca8034SShashidhar Hiremath */ 12462ca8034SShashidhar Hiremath static int dw_mci_pltfm_suspend(struct device *dev) 12562ca8034SShashidhar Hiremath { 12662ca8034SShashidhar Hiremath struct dw_mci *host = dev_get_drvdata(dev); 12762ca8034SShashidhar Hiremath 128dd369800SAndy Shevchenko return dw_mci_suspend(host); 12962ca8034SShashidhar Hiremath } 13062ca8034SShashidhar Hiremath 13162ca8034SShashidhar Hiremath static int dw_mci_pltfm_resume(struct device *dev) 13262ca8034SShashidhar Hiremath { 13362ca8034SShashidhar Hiremath struct dw_mci *host = dev_get_drvdata(dev); 13462ca8034SShashidhar Hiremath 135dd369800SAndy Shevchenko return dw_mci_resume(host); 13662ca8034SShashidhar Hiremath } 13762ca8034SShashidhar Hiremath #else 13862ca8034SShashidhar Hiremath #define dw_mci_pltfm_suspend NULL 13962ca8034SShashidhar Hiremath #define dw_mci_pltfm_resume NULL 14062ca8034SShashidhar Hiremath #endif /* CONFIG_PM_SLEEP */ 14162ca8034SShashidhar Hiremath 14217403f23SThomas Abraham SIMPLE_DEV_PM_OPS(dw_mci_pltfm_pmops, dw_mci_pltfm_suspend, dw_mci_pltfm_resume); 14317403f23SThomas Abraham EXPORT_SYMBOL_GPL(dw_mci_pltfm_pmops); 14462ca8034SShashidhar Hiremath 145c91eab4bSThomas Abraham static const struct of_device_id dw_mci_pltfm_match[] = { 146c91eab4bSThomas Abraham { .compatible = "snps,dw-mshc", }, 147c73e41c8SHeiko Stübner { .compatible = "rockchip,rk2928-dw-mshc", 148f629ba2cSAddy Ke .data = &rk2928_drv_data }, 149f629ba2cSAddy Ke { .compatible = "rockchip,rk3288-dw-mshc", 150f629ba2cSAddy Ke .data = &rk3288_drv_data }, 151ec1e5d70SDinh Nguyen { .compatible = "altr,socfpga-dw-mshc", 152ec1e5d70SDinh Nguyen .data = &socfpga_drv_data }, 153c91eab4bSThomas Abraham {}, 154c91eab4bSThomas Abraham }; 155c91eab4bSThomas Abraham MODULE_DEVICE_TABLE(of, dw_mci_pltfm_match); 156c91eab4bSThomas Abraham 157b177a530SHeiko Stübner static int dw_mci_pltfm_probe(struct platform_device *pdev) 158b177a530SHeiko Stübner { 159c73e41c8SHeiko Stübner const struct dw_mci_drv_data *drv_data = NULL; 160c73e41c8SHeiko Stübner const struct of_device_id *match; 161c73e41c8SHeiko Stübner 162c73e41c8SHeiko Stübner if (pdev->dev.of_node) { 163c73e41c8SHeiko Stübner match = of_match_node(dw_mci_pltfm_match, pdev->dev.of_node); 164c73e41c8SHeiko Stübner drv_data = match->data; 165c73e41c8SHeiko Stübner } 166c73e41c8SHeiko Stübner 167c73e41c8SHeiko Stübner return dw_mci_pltfm_register(pdev, drv_data); 168b177a530SHeiko Stübner } 169b177a530SHeiko Stübner 170b177a530SHeiko Stübner int dw_mci_pltfm_remove(struct platform_device *pdev) 171b177a530SHeiko Stübner { 172b177a530SHeiko Stübner struct dw_mci *host = platform_get_drvdata(pdev); 173b177a530SHeiko Stübner 174b177a530SHeiko Stübner dw_mci_remove(host); 175b177a530SHeiko Stübner return 0; 176b177a530SHeiko Stübner } 177b177a530SHeiko Stübner EXPORT_SYMBOL_GPL(dw_mci_pltfm_remove); 178b177a530SHeiko Stübner 17962ca8034SShashidhar Hiremath static struct platform_driver dw_mci_pltfm_driver = { 18049480cf2SAndy Shevchenko .probe = dw_mci_pltfm_probe, 1814e608e4eSGreg Kroah-Hartman .remove = dw_mci_pltfm_remove, 18262ca8034SShashidhar Hiremath .driver = { 18362ca8034SShashidhar Hiremath .name = "dw_mmc", 184cf109bc0SSachin Kamat .of_match_table = dw_mci_pltfm_match, 18562ca8034SShashidhar Hiremath .pm = &dw_mci_pltfm_pmops, 18662ca8034SShashidhar Hiremath }, 18762ca8034SShashidhar Hiremath }; 18862ca8034SShashidhar Hiremath 18949480cf2SAndy Shevchenko module_platform_driver(dw_mci_pltfm_driver); 19062ca8034SShashidhar Hiremath 19162ca8034SShashidhar Hiremath MODULE_DESCRIPTION("DW Multimedia Card Interface driver"); 19262ca8034SShashidhar Hiremath MODULE_AUTHOR("NXP Semiconductor VietNam"); 19362ca8034SShashidhar Hiremath MODULE_AUTHOR("Imagination Technologies Ltd"); 19462ca8034SShashidhar Hiremath MODULE_LICENSE("GPL v2"); 195