|  | /* | 
|  | * Support of SDHCI platform devices for Microchip PIC32. | 
|  | * | 
|  | * Copyright (C) 2015 Microchip | 
|  | * Andrei Pistirica, Paul Thacker | 
|  | * | 
|  | * Inspired by sdhci-pltfm.c | 
|  | * | 
|  | * This file is licensed under the terms of the GNU General Public | 
|  | * License version 2. This program is licensed "as is" without any | 
|  | * warranty of any kind, whether express or implied. | 
|  | */ | 
|  |  | 
|  | #include <linux/clk.h> | 
|  | #include <linux/delay.h> | 
|  | #include <linux/highmem.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/interrupt.h> | 
|  | #include <linux/irq.h> | 
|  | #include <linux/of.h> | 
|  | #include <linux/platform_device.h> | 
|  | #include <linux/pm.h> | 
|  | #include <linux/slab.h> | 
|  | #include <linux/mmc/host.h> | 
|  | #include <linux/io.h> | 
|  | #include "sdhci.h" | 
|  | #include "sdhci-pltfm.h" | 
|  | #include <linux/platform_data/sdhci-pic32.h> | 
|  |  | 
|  | #define SDH_SHARED_BUS_CTRL		0x000000E0 | 
|  | #define SDH_SHARED_BUS_NR_CLK_PINS_MASK	0x7 | 
|  | #define SDH_SHARED_BUS_NR_IRQ_PINS_MASK	0x30 | 
|  | #define SDH_SHARED_BUS_CLK_PINS		0x10 | 
|  | #define SDH_SHARED_BUS_IRQ_PINS		0x14 | 
|  | #define SDH_CAPS_SDH_SLOT_TYPE_MASK	0xC0000000 | 
|  | #define SDH_SLOT_TYPE_REMOVABLE		0x0 | 
|  | #define SDH_SLOT_TYPE_EMBEDDED		0x1 | 
|  | #define SDH_SLOT_TYPE_SHARED_BUS	0x2 | 
|  | #define SDHCI_CTRL_CDSSEL		0x80 | 
|  | #define SDHCI_CTRL_CDTLVL		0x40 | 
|  |  | 
|  | #define ADMA_FIFO_RD_THSHLD	512 | 
|  | #define ADMA_FIFO_WR_THSHLD	512 | 
|  |  | 
|  | struct pic32_sdhci_priv { | 
|  | struct platform_device	*pdev; | 
|  | struct clk *sys_clk; | 
|  | struct clk *base_clk; | 
|  | }; | 
|  |  | 
|  | static unsigned int pic32_sdhci_get_max_clock(struct sdhci_host *host) | 
|  | { | 
|  | struct pic32_sdhci_priv *sdhci_pdata = sdhci_priv(host); | 
|  |  | 
|  | return clk_get_rate(sdhci_pdata->base_clk); | 
|  | } | 
|  |  | 
|  | static void pic32_sdhci_set_bus_width(struct sdhci_host *host, int width) | 
|  | { | 
|  | u8 ctrl; | 
|  |  | 
|  | ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL); | 
|  | if (width == MMC_BUS_WIDTH_8) { | 
|  | ctrl &= ~SDHCI_CTRL_4BITBUS; | 
|  | if (host->version >= SDHCI_SPEC_300) | 
|  | ctrl |= SDHCI_CTRL_8BITBUS; | 
|  | } else { | 
|  | if (host->version >= SDHCI_SPEC_300) | 
|  | ctrl &= ~SDHCI_CTRL_8BITBUS; | 
|  | if (width == MMC_BUS_WIDTH_4) | 
|  | ctrl |= SDHCI_CTRL_4BITBUS; | 
|  | else | 
|  | ctrl &= ~SDHCI_CTRL_4BITBUS; | 
|  | } | 
|  |  | 
|  | /* CD select and test bits must be set for errata workaround. */ | 
|  | ctrl &= ~SDHCI_CTRL_CDTLVL; | 
|  | ctrl |= SDHCI_CTRL_CDSSEL; | 
|  | sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL); | 
|  | } | 
|  |  | 
|  | static unsigned int pic32_sdhci_get_ro(struct sdhci_host *host) | 
|  | { | 
|  | /* | 
|  | * The SDHCI_WRITE_PROTECT bit is unstable on current hardware so we | 
|  | * can't depend on its value in any way. | 
|  | */ | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static const struct sdhci_ops pic32_sdhci_ops = { | 
|  | .get_max_clock = pic32_sdhci_get_max_clock, | 
|  | .set_clock = sdhci_set_clock, | 
|  | .set_bus_width = pic32_sdhci_set_bus_width, | 
|  | .reset = sdhci_reset, | 
|  | .set_uhs_signaling = sdhci_set_uhs_signaling, | 
|  | .get_ro = pic32_sdhci_get_ro, | 
|  | }; | 
|  |  | 
|  | static const struct sdhci_pltfm_data sdhci_pic32_pdata = { | 
|  | .ops = &pic32_sdhci_ops, | 
|  | .quirks = SDHCI_QUIRK_NO_HISPD_BIT, | 
|  | .quirks2 = SDHCI_QUIRK2_NO_1_8_V, | 
|  | }; | 
|  |  | 
|  | static void pic32_sdhci_shared_bus(struct platform_device *pdev) | 
|  | { | 
|  | struct sdhci_host *host = platform_get_drvdata(pdev); | 
|  | u32 bus = readl(host->ioaddr + SDH_SHARED_BUS_CTRL); | 
|  | u32 clk_pins = (bus & SDH_SHARED_BUS_NR_CLK_PINS_MASK) >> 0; | 
|  | u32 irq_pins = (bus & SDH_SHARED_BUS_NR_IRQ_PINS_MASK) >> 4; | 
|  |  | 
|  | /* select first clock */ | 
|  | if (clk_pins & 1) | 
|  | bus |= (1 << SDH_SHARED_BUS_CLK_PINS); | 
|  |  | 
|  | /* select first interrupt */ | 
|  | if (irq_pins & 1) | 
|  | bus |= (1 << SDH_SHARED_BUS_IRQ_PINS); | 
|  |  | 
|  | writel(bus, host->ioaddr + SDH_SHARED_BUS_CTRL); | 
|  | } | 
|  |  | 
|  | static int pic32_sdhci_probe_platform(struct platform_device *pdev, | 
|  | struct pic32_sdhci_priv *pdata) | 
|  | { | 
|  | int ret = 0; | 
|  | u32 caps_slot_type; | 
|  | struct sdhci_host *host = platform_get_drvdata(pdev); | 
|  |  | 
|  | /* Check card slot connected on shared bus. */ | 
|  | host->caps = readl(host->ioaddr + SDHCI_CAPABILITIES); | 
|  | caps_slot_type = (host->caps & SDH_CAPS_SDH_SLOT_TYPE_MASK) >> 30; | 
|  | if (caps_slot_type == SDH_SLOT_TYPE_SHARED_BUS) | 
|  | pic32_sdhci_shared_bus(pdev); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int pic32_sdhci_probe(struct platform_device *pdev) | 
|  | { | 
|  | struct sdhci_host *host; | 
|  | struct sdhci_pltfm_host *pltfm_host; | 
|  | struct pic32_sdhci_priv *sdhci_pdata; | 
|  | struct pic32_sdhci_platform_data *plat_data; | 
|  | int ret; | 
|  |  | 
|  | host = sdhci_pltfm_init(pdev, &sdhci_pic32_pdata, | 
|  | sizeof(struct pic32_sdhci_priv)); | 
|  | if (IS_ERR(host)) { | 
|  | ret = PTR_ERR(host); | 
|  | goto err; | 
|  | } | 
|  |  | 
|  | pltfm_host = sdhci_priv(host); | 
|  | sdhci_pdata = sdhci_pltfm_priv(pltfm_host); | 
|  |  | 
|  | plat_data = pdev->dev.platform_data; | 
|  | if (plat_data && plat_data->setup_dma) { | 
|  | ret = plat_data->setup_dma(ADMA_FIFO_RD_THSHLD, | 
|  | ADMA_FIFO_WR_THSHLD); | 
|  | if (ret) | 
|  | goto err_host; | 
|  | } | 
|  |  | 
|  | sdhci_pdata->sys_clk = devm_clk_get(&pdev->dev, "sys_clk"); | 
|  | if (IS_ERR(sdhci_pdata->sys_clk)) { | 
|  | ret = PTR_ERR(sdhci_pdata->sys_clk); | 
|  | dev_err(&pdev->dev, "Error getting clock\n"); | 
|  | goto err_host; | 
|  | } | 
|  |  | 
|  | ret = clk_prepare_enable(sdhci_pdata->sys_clk); | 
|  | if (ret) { | 
|  | dev_err(&pdev->dev, "Error enabling clock\n"); | 
|  | goto err_host; | 
|  | } | 
|  |  | 
|  | sdhci_pdata->base_clk = devm_clk_get(&pdev->dev, "base_clk"); | 
|  | if (IS_ERR(sdhci_pdata->base_clk)) { | 
|  | ret = PTR_ERR(sdhci_pdata->base_clk); | 
|  | dev_err(&pdev->dev, "Error getting clock\n"); | 
|  | goto err_sys_clk; | 
|  | } | 
|  |  | 
|  | ret = clk_prepare_enable(sdhci_pdata->base_clk); | 
|  | if (ret) { | 
|  | dev_err(&pdev->dev, "Error enabling clock\n"); | 
|  | goto err_base_clk; | 
|  | } | 
|  |  | 
|  | ret = mmc_of_parse(host->mmc); | 
|  | if (ret) | 
|  | goto err_base_clk; | 
|  |  | 
|  | ret = pic32_sdhci_probe_platform(pdev, sdhci_pdata); | 
|  | if (ret) { | 
|  | dev_err(&pdev->dev, "failed to probe platform!\n"); | 
|  | goto err_base_clk; | 
|  | } | 
|  |  | 
|  | ret = sdhci_add_host(host); | 
|  | if (ret) | 
|  | goto err_base_clk; | 
|  |  | 
|  | dev_info(&pdev->dev, "Successfully added sdhci host\n"); | 
|  | return 0; | 
|  |  | 
|  | err_base_clk: | 
|  | clk_disable_unprepare(sdhci_pdata->base_clk); | 
|  | err_sys_clk: | 
|  | clk_disable_unprepare(sdhci_pdata->sys_clk); | 
|  | err_host: | 
|  | sdhci_pltfm_free(pdev); | 
|  | err: | 
|  | dev_err(&pdev->dev, "pic32-sdhci probe failed: %d\n", ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int pic32_sdhci_remove(struct platform_device *pdev) | 
|  | { | 
|  | struct sdhci_host *host = platform_get_drvdata(pdev); | 
|  | struct pic32_sdhci_priv *sdhci_pdata = sdhci_priv(host); | 
|  | u32 scratch; | 
|  |  | 
|  | scratch = readl(host->ioaddr + SDHCI_INT_STATUS); | 
|  | sdhci_remove_host(host, scratch == (u32)~0); | 
|  | clk_disable_unprepare(sdhci_pdata->base_clk); | 
|  | clk_disable_unprepare(sdhci_pdata->sys_clk); | 
|  | sdhci_pltfm_free(pdev); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static const struct of_device_id pic32_sdhci_id_table[] = { | 
|  | { .compatible = "microchip,pic32mzda-sdhci" }, | 
|  | {} | 
|  | }; | 
|  | MODULE_DEVICE_TABLE(of, pic32_sdhci_id_table); | 
|  |  | 
|  | static struct platform_driver pic32_sdhci_driver = { | 
|  | .driver = { | 
|  | .name	= "pic32-sdhci", | 
|  | .of_match_table = of_match_ptr(pic32_sdhci_id_table), | 
|  | }, | 
|  | .probe		= pic32_sdhci_probe, | 
|  | .remove		= pic32_sdhci_remove, | 
|  | }; | 
|  |  | 
|  | module_platform_driver(pic32_sdhci_driver); | 
|  |  | 
|  | MODULE_DESCRIPTION("Microchip PIC32 SDHCI driver"); | 
|  | MODULE_AUTHOR("Pistirica Sorin Andrei & Sandeep Sheriker"); | 
|  | MODULE_LICENSE("GPL v2"); |