Linux Audio

Check our new training course

Loading...
Note: File does not exist in v4.6.
  1/*
  2 * Copyright (C) 2016 Socionext Inc.
  3 *   Author: Masahiro Yamada <yamada.masahiro@socionext.com>
  4 *
  5 * This program is free software; you can redistribute it and/or modify
  6 * it under the terms of the GNU General Public License as published by
  7 * the Free Software Foundation; either version 2 of the License, or
  8 * (at your option) any later version.
  9 *
 10 * This program is distributed in the hope that it will be useful,
 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 13 * GNU General Public License for more details.
 14 */
 15
 16#include <linux/bitops.h>
 17#include <linux/iopoll.h>
 18#include <linux/module.h>
 19#include <linux/mmc/host.h>
 20
 21#include "sdhci-pltfm.h"
 22
 23/* HRS - Host Register Set (specific to Cadence) */
 24#define SDHCI_CDNS_HRS04		0x10		/* PHY access port */
 25#define   SDHCI_CDNS_HRS04_ACK			BIT(26)
 26#define   SDHCI_CDNS_HRS04_RD			BIT(25)
 27#define   SDHCI_CDNS_HRS04_WR			BIT(24)
 28#define   SDHCI_CDNS_HRS04_RDATA_SHIFT		12
 29#define   SDHCI_CDNS_HRS04_WDATA_SHIFT		8
 30#define   SDHCI_CDNS_HRS04_ADDR_SHIFT		0
 31
 32#define SDHCI_CDNS_HRS06		0x18		/* eMMC control */
 33#define   SDHCI_CDNS_HRS06_TUNE_UP		BIT(15)
 34#define   SDHCI_CDNS_HRS06_TUNE_SHIFT		8
 35#define   SDHCI_CDNS_HRS06_TUNE_MASK		0x3f
 36#define   SDHCI_CDNS_HRS06_MODE_MASK		0x7
 37#define   SDHCI_CDNS_HRS06_MODE_SD		0x0
 38#define   SDHCI_CDNS_HRS06_MODE_MMC_SDR		0x2
 39#define   SDHCI_CDNS_HRS06_MODE_MMC_DDR		0x3
 40#define   SDHCI_CDNS_HRS06_MODE_MMC_HS200	0x4
 41#define   SDHCI_CDNS_HRS06_MODE_MMC_HS400	0x5
 42
 43/* SRS - Slot Register Set (SDHCI-compatible) */
 44#define SDHCI_CDNS_SRS_BASE		0x200
 45
 46/* PHY */
 47#define SDHCI_CDNS_PHY_DLY_SD_HS	0x00
 48#define SDHCI_CDNS_PHY_DLY_SD_DEFAULT	0x01
 49#define SDHCI_CDNS_PHY_DLY_UHS_SDR12	0x02
 50#define SDHCI_CDNS_PHY_DLY_UHS_SDR25	0x03
 51#define SDHCI_CDNS_PHY_DLY_UHS_SDR50	0x04
 52#define SDHCI_CDNS_PHY_DLY_UHS_DDR50	0x05
 53#define SDHCI_CDNS_PHY_DLY_EMMC_LEGACY	0x06
 54#define SDHCI_CDNS_PHY_DLY_EMMC_SDR	0x07
 55#define SDHCI_CDNS_PHY_DLY_EMMC_DDR	0x08
 56
 57/*
 58 * The tuned val register is 6 bit-wide, but not the whole of the range is
 59 * available.  The range 0-42 seems to be available (then 43 wraps around to 0)
 60 * but I am not quite sure if it is official.  Use only 0 to 39 for safety.
 61 */
 62#define SDHCI_CDNS_MAX_TUNING_LOOP	40
 63
 64struct sdhci_cdns_priv {
 65	void __iomem *hrs_addr;
 66};
 67
 68static void sdhci_cdns_write_phy_reg(struct sdhci_cdns_priv *priv,
 69				     u8 addr, u8 data)
 70{
 71	void __iomem *reg = priv->hrs_addr + SDHCI_CDNS_HRS04;
 72	u32 tmp;
 73
 74	tmp = (data << SDHCI_CDNS_HRS04_WDATA_SHIFT) |
 75	      (addr << SDHCI_CDNS_HRS04_ADDR_SHIFT);
 76	writel(tmp, reg);
 77
 78	tmp |= SDHCI_CDNS_HRS04_WR;
 79	writel(tmp, reg);
 80
 81	tmp &= ~SDHCI_CDNS_HRS04_WR;
 82	writel(tmp, reg);
 83}
 84
 85static void sdhci_cdns_phy_init(struct sdhci_cdns_priv *priv)
 86{
 87	sdhci_cdns_write_phy_reg(priv, SDHCI_CDNS_PHY_DLY_SD_HS, 4);
 88	sdhci_cdns_write_phy_reg(priv, SDHCI_CDNS_PHY_DLY_SD_DEFAULT, 4);
 89	sdhci_cdns_write_phy_reg(priv, SDHCI_CDNS_PHY_DLY_EMMC_LEGACY, 9);
 90	sdhci_cdns_write_phy_reg(priv, SDHCI_CDNS_PHY_DLY_EMMC_SDR, 2);
 91	sdhci_cdns_write_phy_reg(priv, SDHCI_CDNS_PHY_DLY_EMMC_DDR, 3);
 92}
 93
 94static inline void *sdhci_cdns_priv(struct sdhci_host *host)
 95{
 96	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
 97
 98	return sdhci_pltfm_priv(pltfm_host);
 99}
100
101static unsigned int sdhci_cdns_get_timeout_clock(struct sdhci_host *host)
102{
103	/*
104	 * Cadence's spec says the Timeout Clock Frequency is the same as the
105	 * Base Clock Frequency.  Divide it by 1000 to return a value in kHz.
106	 */
107	return host->max_clk / 1000;
108}
109
110static void sdhci_cdns_set_uhs_signaling(struct sdhci_host *host,
111					 unsigned int timing)
112{
113	struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host);
114	u32 mode, tmp;
115
116	switch (timing) {
117	case MMC_TIMING_MMC_HS:
118		mode = SDHCI_CDNS_HRS06_MODE_MMC_SDR;
119		break;
120	case MMC_TIMING_MMC_DDR52:
121		mode = SDHCI_CDNS_HRS06_MODE_MMC_DDR;
122		break;
123	case MMC_TIMING_MMC_HS200:
124		mode = SDHCI_CDNS_HRS06_MODE_MMC_HS200;
125		break;
126	case MMC_TIMING_MMC_HS400:
127		mode = SDHCI_CDNS_HRS06_MODE_MMC_HS400;
128		break;
129	default:
130		mode = SDHCI_CDNS_HRS06_MODE_SD;
131		break;
132	}
133
134	/* The speed mode for eMMC is selected by HRS06 register */
135	tmp = readl(priv->hrs_addr + SDHCI_CDNS_HRS06);
136	tmp &= ~SDHCI_CDNS_HRS06_MODE_MASK;
137	tmp |= mode;
138	writel(tmp, priv->hrs_addr + SDHCI_CDNS_HRS06);
139
140	/* For SD, fall back to the default handler */
141	if (mode == SDHCI_CDNS_HRS06_MODE_SD)
142		sdhci_set_uhs_signaling(host, timing);
143}
144
145static const struct sdhci_ops sdhci_cdns_ops = {
146	.set_clock = sdhci_set_clock,
147	.get_timeout_clock = sdhci_cdns_get_timeout_clock,
148	.set_bus_width = sdhci_set_bus_width,
149	.reset = sdhci_reset,
150	.set_uhs_signaling = sdhci_cdns_set_uhs_signaling,
151};
152
153static const struct sdhci_pltfm_data sdhci_cdns_pltfm_data = {
154	.ops = &sdhci_cdns_ops,
155};
156
157static int sdhci_cdns_set_tune_val(struct sdhci_host *host, unsigned int val)
158{
159	struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host);
160	void __iomem *reg = priv->hrs_addr + SDHCI_CDNS_HRS06;
161	u32 tmp;
162
163	if (WARN_ON(val > SDHCI_CDNS_HRS06_TUNE_MASK))
164		return -EINVAL;
165
166	tmp = readl(reg);
167	tmp &= ~(SDHCI_CDNS_HRS06_TUNE_MASK << SDHCI_CDNS_HRS06_TUNE_SHIFT);
168	tmp |= val << SDHCI_CDNS_HRS06_TUNE_SHIFT;
169	tmp |= SDHCI_CDNS_HRS06_TUNE_UP;
170	writel(tmp, reg);
171
172	return readl_poll_timeout(reg, tmp, !(tmp & SDHCI_CDNS_HRS06_TUNE_UP),
173				  0, 1);
174}
175
176static int sdhci_cdns_execute_tuning(struct mmc_host *mmc, u32 opcode)
177{
178	struct sdhci_host *host = mmc_priv(mmc);
179	int cur_streak = 0;
180	int max_streak = 0;
181	int end_of_streak = 0;
182	int i;
183
184	/*
185	 * This handler only implements the eMMC tuning that is specific to
186	 * this controller.  Fall back to the standard method for SD timing.
187	 */
188	if (host->timing != MMC_TIMING_MMC_HS200)
189		return sdhci_execute_tuning(mmc, opcode);
190
191	if (WARN_ON(opcode != MMC_SEND_TUNING_BLOCK_HS200))
192		return -EINVAL;
193
194	for (i = 0; i < SDHCI_CDNS_MAX_TUNING_LOOP; i++) {
195		if (sdhci_cdns_set_tune_val(host, i) ||
196		    mmc_send_tuning(host->mmc, opcode, NULL)) { /* bad */
197			cur_streak = 0;
198		} else { /* good */
199			cur_streak++;
200			if (cur_streak > max_streak) {
201				max_streak = cur_streak;
202				end_of_streak = i;
203			}
204		}
205	}
206
207	if (!max_streak) {
208		dev_err(mmc_dev(host->mmc), "no tuning point found\n");
209		return -EIO;
210	}
211
212	return sdhci_cdns_set_tune_val(host, end_of_streak - max_streak / 2);
213}
214
215static int sdhci_cdns_probe(struct platform_device *pdev)
216{
217	struct sdhci_host *host;
218	struct sdhci_pltfm_host *pltfm_host;
219	struct sdhci_cdns_priv *priv;
220	struct clk *clk;
221	int ret;
222
223	clk = devm_clk_get(&pdev->dev, NULL);
224	if (IS_ERR(clk))
225		return PTR_ERR(clk);
226
227	ret = clk_prepare_enable(clk);
228	if (ret)
229		return ret;
230
231	host = sdhci_pltfm_init(pdev, &sdhci_cdns_pltfm_data, sizeof(*priv));
232	if (IS_ERR(host)) {
233		ret = PTR_ERR(host);
234		goto disable_clk;
235	}
236
237	pltfm_host = sdhci_priv(host);
238	pltfm_host->clk = clk;
239
240	priv = sdhci_cdns_priv(host);
241	priv->hrs_addr = host->ioaddr;
242	host->ioaddr += SDHCI_CDNS_SRS_BASE;
243	host->mmc_host_ops.execute_tuning = sdhci_cdns_execute_tuning;
244
245	ret = mmc_of_parse(host->mmc);
246	if (ret)
247		goto free;
248
249	sdhci_cdns_phy_init(priv);
250
251	ret = sdhci_add_host(host);
252	if (ret)
253		goto free;
254
255	return 0;
256free:
257	sdhci_pltfm_free(pdev);
258disable_clk:
259	clk_disable_unprepare(clk);
260
261	return ret;
262}
263
264static const struct of_device_id sdhci_cdns_match[] = {
265	{ .compatible = "socionext,uniphier-sd4hc" },
266	{ .compatible = "cdns,sd4hc" },
267	{ /* sentinel */ }
268};
269MODULE_DEVICE_TABLE(of, sdhci_cdns_match);
270
271static struct platform_driver sdhci_cdns_driver = {
272	.driver = {
273		.name = "sdhci-cdns",
274		.pm = &sdhci_pltfm_pmops,
275		.of_match_table = sdhci_cdns_match,
276	},
277	.probe = sdhci_cdns_probe,
278	.remove = sdhci_pltfm_unregister,
279};
280module_platform_driver(sdhci_cdns_driver);
281
282MODULE_AUTHOR("Masahiro Yamada <yamada.masahiro@socionext.com>");
283MODULE_DESCRIPTION("Cadence SD/SDIO/eMMC Host Controller Driver");
284MODULE_LICENSE("GPL");