Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-only
0002 /*
0003  * SDHCI support for CNS3xxx SoC
0004  *
0005  * Copyright 2008 Cavium Networks
0006  * Copyright 2010 MontaVista Software, LLC.
0007  *
0008  * Authors: Scott Shu
0009  *      Anton Vorontsov <avorontsov@mvista.com>
0010  */
0011 
0012 #include <linux/delay.h>
0013 #include <linux/device.h>
0014 #include <linux/mmc/host.h>
0015 #include <linux/module.h>
0016 #include "sdhci-pltfm.h"
0017 
0018 static unsigned int sdhci_cns3xxx_get_max_clk(struct sdhci_host *host)
0019 {
0020     return 150000000;
0021 }
0022 
0023 static void sdhci_cns3xxx_set_clock(struct sdhci_host *host, unsigned int clock)
0024 {
0025     struct device *dev = mmc_dev(host->mmc);
0026     int div = 1;
0027     u16 clk;
0028     unsigned long timeout;
0029 
0030     host->mmc->actual_clock = 0;
0031 
0032     sdhci_writew(host, 0, SDHCI_CLOCK_CONTROL);
0033 
0034     if (clock == 0)
0035         return;
0036 
0037     while (host->max_clk / div > clock) {
0038         /*
0039          * On CNS3xxx divider grows linearly up to 4, and then
0040          * exponentially up to 256.
0041          */
0042         if (div < 4)
0043             div += 1;
0044         else if (div < 256)
0045             div *= 2;
0046         else
0047             break;
0048     }
0049 
0050     dev_dbg(dev, "desired SD clock: %d, actual: %d\n",
0051         clock, host->max_clk / div);
0052 
0053     /* Divide by 3 is special. */
0054     if (div != 3)
0055         div >>= 1;
0056 
0057     clk = div << SDHCI_DIVIDER_SHIFT;
0058     clk |= SDHCI_CLOCK_INT_EN;
0059     sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL);
0060 
0061     timeout = 20;
0062     while (!((clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL))
0063             & SDHCI_CLOCK_INT_STABLE)) {
0064         if (timeout == 0) {
0065             dev_warn(dev, "clock is unstable");
0066             break;
0067         }
0068         timeout--;
0069         mdelay(1);
0070     }
0071 
0072     clk |= SDHCI_CLOCK_CARD_EN;
0073     sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL);
0074 }
0075 
0076 static const struct sdhci_ops sdhci_cns3xxx_ops = {
0077     .get_max_clock  = sdhci_cns3xxx_get_max_clk,
0078     .set_clock  = sdhci_cns3xxx_set_clock,
0079     .set_bus_width  = sdhci_set_bus_width,
0080     .reset          = sdhci_reset,
0081     .set_uhs_signaling = sdhci_set_uhs_signaling,
0082 };
0083 
0084 static const struct sdhci_pltfm_data sdhci_cns3xxx_pdata = {
0085     .ops = &sdhci_cns3xxx_ops,
0086     .quirks = SDHCI_QUIRK_BROKEN_DMA |
0087           SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK |
0088           SDHCI_QUIRK_INVERTED_WRITE_PROTECT |
0089           SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN |
0090           SDHCI_QUIRK_BROKEN_TIMEOUT_VAL,
0091 };
0092 
0093 static int sdhci_cns3xxx_probe(struct platform_device *pdev)
0094 {
0095     return sdhci_pltfm_register(pdev, &sdhci_cns3xxx_pdata, 0);
0096 }
0097 
0098 static struct platform_driver sdhci_cns3xxx_driver = {
0099     .driver     = {
0100         .name   = "sdhci-cns3xxx",
0101         .probe_type = PROBE_PREFER_ASYNCHRONOUS,
0102         .pm = &sdhci_pltfm_pmops,
0103     },
0104     .probe      = sdhci_cns3xxx_probe,
0105     .remove     = sdhci_pltfm_unregister,
0106 };
0107 
0108 module_platform_driver(sdhci_cns3xxx_driver);
0109 
0110 MODULE_DESCRIPTION("SDHCI driver for CNS3xxx");
0111 MODULE_AUTHOR("Scott Shu, "
0112           "Anton Vorontsov <avorontsov@mvista.com>");
0113 MODULE_LICENSE("GPL v2");