1/* 2 * SDHCI support for CNS3xxx SoC 3 * 4 * Copyright 2008 Cavium Networks 5 * Copyright 2010 MontaVista Software, LLC. 6 * 7 * Authors: Scott Shu 8 * Anton Vorontsov <avorontsov@mvista.com> 9 * 10 * This program is free software; you can redistribute it and/or modify 11 * it under the terms of the GNU General Public License version 2 as 12 * published by the Free Software Foundation. 13 */ 14 15#include <linux/delay.h> 16#include <linux/device.h> 17#include <linux/mmc/host.h> 18#include <linux/module.h> 19#include "sdhci-pltfm.h" 20 21static unsigned int sdhci_cns3xxx_get_max_clk(struct sdhci_host *host) 22{ 23 return 150000000; 24} 25 26static void sdhci_cns3xxx_set_clock(struct sdhci_host *host, unsigned int clock) 27{ 28 struct device *dev = mmc_dev(host->mmc); 29 int div = 1; 30 u16 clk; 31 unsigned long timeout; 32 33 host->mmc->actual_clock = 0; 34 35 sdhci_writew(host, 0, SDHCI_CLOCK_CONTROL); 36 37 if (clock == 0) 38 return; 39 40 while (host->max_clk / div > clock) { 41 /* 42 * On CNS3xxx divider grows linearly up to 4, and then 43 * exponentially up to 256. 44 */ 45 if (div < 4) 46 div += 1; 47 else if (div < 256) 48 div *= 2; 49 else 50 break; 51 } 52 53 dev_dbg(dev, "desired SD clock: %d, actual: %d\n", 54 clock, host->max_clk / div); 55 56 /* Divide by 3 is special. */ 57 if (div != 3) 58 div >>= 1; 59 60 clk = div << SDHCI_DIVIDER_SHIFT; 61 clk |= SDHCI_CLOCK_INT_EN; 62 sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL); 63 64 timeout = 20; 65 while (!((clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL)) 66 & SDHCI_CLOCK_INT_STABLE)) { 67 if (timeout == 0) { 68 dev_warn(dev, "clock is unstable"); 69 break; 70 } 71 timeout--; 72 mdelay(1); 73 } 74 75 clk |= SDHCI_CLOCK_CARD_EN; 76 sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL); 77} 78 79static const struct sdhci_ops sdhci_cns3xxx_ops = { 80 .get_max_clock = sdhci_cns3xxx_get_max_clk, 81 .set_clock = sdhci_cns3xxx_set_clock, 82 .set_bus_width = sdhci_set_bus_width, 83 .reset = sdhci_reset, 84 .set_uhs_signaling = sdhci_set_uhs_signaling, 85}; 86 87static const struct sdhci_pltfm_data sdhci_cns3xxx_pdata = { 88 .ops = &sdhci_cns3xxx_ops, 89 .quirks = SDHCI_QUIRK_BROKEN_DMA | 90 SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK | 91 SDHCI_QUIRK_INVERTED_WRITE_PROTECT | 92 SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN | 93 SDHCI_QUIRK_BROKEN_TIMEOUT_VAL, 94}; 95 96static int sdhci_cns3xxx_probe(struct platform_device *pdev) 97{ 98 return sdhci_pltfm_register(pdev, &sdhci_cns3xxx_pdata, 0); 99} 100 101static struct platform_driver sdhci_cns3xxx_driver = { 102 .driver = { 103 .name = "sdhci-cns3xxx", 104 .pm = SDHCI_PLTFM_PMOPS, 105 }, 106 .probe = sdhci_cns3xxx_probe, 107 .remove = sdhci_pltfm_unregister, 108}; 109 110module_platform_driver(sdhci_cns3xxx_driver); 111 112MODULE_DESCRIPTION("SDHCI driver for CNS3xxx"); 113MODULE_AUTHOR("Scott Shu, " 114 "Anton Vorontsov <avorontsov@mvista.com>"); 115MODULE_LICENSE("GPL v2"); 116