-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This patch adds new host controller driver for Fujitsu SDHCI controller f_sdh30. Signed-off-by: Vincent Yang <Vincent.Yang@tw.fujitsu.com> Signed-off-by: Andy Green <andy.green@linaro.org> Signed-off-by: Tetsuya Takinishi <t.takinishi@jp.fujitsu.com> Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
- Loading branch information
Vincent Yang
authored and
Ulf Hansson
committed
Jan 20, 2015
1 parent
d3fc5d7
commit 87a5074
Showing
4 changed files
with
279 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
* Fujitsu SDHCI controller | ||
|
||
This file documents differences between the core properties in mmc.txt | ||
and the properties used by the sdhci_f_sdh30 driver. | ||
|
||
Required properties: | ||
- compatible: "fujitsu,mb86s70-sdhci-3.0" | ||
- clocks: Must contain an entry for each entry in clock-names. It is a | ||
list of phandles and clock-specifier pairs. | ||
See ../clocks/clock-bindings.txt for details. | ||
- clock-names: Should contain the following two entries: | ||
"iface" - clock used for sdhci interface | ||
"core" - core clock for sdhci controller | ||
|
||
Optional properties: | ||
- vqmmc-supply: phandle to the regulator device tree node, mentioned | ||
as the VCCQ/VDD_IO supply in the eMMC/SD specs. | ||
|
||
Example: | ||
|
||
sdhci1: mmc@36600000 { | ||
compatible = "fujitsu,mb86s70-sdhci-3.0"; | ||
reg = <0 0x36600000 0x1000>; | ||
interrupts = <0 172 0x4>, | ||
<0 173 0x4>; | ||
bus-width = <4>; | ||
vqmmc-supply = <&vccq_sdhci1>; | ||
clocks = <&clock 2 2 0>, <&clock 2 3 0>; | ||
clock-names = "iface", "core"; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,237 @@ | ||
/* | ||
* linux/drivers/mmc/host/sdhci_f_sdh30.c | ||
* | ||
* Copyright (C) 2013 - 2015 Fujitsu Semiconductor, Ltd | ||
* Vincent Yang <vincent.yang@tw.fujitsu.com> | ||
* Copyright (C) 2015 Linaro Ltd Andy Green <andy.green@linaro.org> | ||
* | ||
* This program is free software: you can redistribute it and/or modify | ||
* it under the terms of the GNU General Public License as published by | ||
* the Free Software Foundation, version 2 of the License. | ||
*/ | ||
|
||
#include <linux/err.h> | ||
#include <linux/delay.h> | ||
#include <linux/module.h> | ||
#include <linux/clk.h> | ||
|
||
#include "sdhci-pltfm.h" | ||
|
||
/* F_SDH30 extended Controller registers */ | ||
#define F_SDH30_AHB_CONFIG 0x100 | ||
#define F_SDH30_AHB_BIGED 0x00000040 | ||
#define F_SDH30_BUSLOCK_DMA 0x00000020 | ||
#define F_SDH30_BUSLOCK_EN 0x00000010 | ||
#define F_SDH30_SIN 0x00000008 | ||
#define F_SDH30_AHB_INCR_16 0x00000004 | ||
#define F_SDH30_AHB_INCR_8 0x00000002 | ||
#define F_SDH30_AHB_INCR_4 0x00000001 | ||
|
||
#define F_SDH30_TUNING_SETTING 0x108 | ||
#define F_SDH30_CMD_CHK_DIS 0x00010000 | ||
|
||
#define F_SDH30_IO_CONTROL2 0x114 | ||
#define F_SDH30_CRES_O_DN 0x00080000 | ||
#define F_SDH30_MSEL_O_1_8 0x00040000 | ||
|
||
#define F_SDH30_ESD_CONTROL 0x124 | ||
#define F_SDH30_EMMC_RST 0x00000002 | ||
#define F_SDH30_EMMC_HS200 0x01000000 | ||
|
||
#define F_SDH30_CMD_DAT_DELAY 0x200 | ||
|
||
#define F_SDH30_MIN_CLOCK 400000 | ||
|
||
struct f_sdhost_priv { | ||
struct clk *clk_iface; | ||
struct clk *clk; | ||
u32 vendor_hs200; | ||
struct device *dev; | ||
}; | ||
|
||
void sdhci_f_sdh30_soft_voltage_switch(struct sdhci_host *host) | ||
{ | ||
struct f_sdhost_priv *priv = sdhci_priv(host); | ||
u32 ctrl = 0; | ||
|
||
usleep_range(2500, 3000); | ||
ctrl = sdhci_readl(host, F_SDH30_IO_CONTROL2); | ||
ctrl |= F_SDH30_CRES_O_DN; | ||
sdhci_writel(host, ctrl, F_SDH30_IO_CONTROL2); | ||
ctrl |= F_SDH30_MSEL_O_1_8; | ||
sdhci_writel(host, ctrl, F_SDH30_IO_CONTROL2); | ||
|
||
ctrl &= ~F_SDH30_CRES_O_DN; | ||
sdhci_writel(host, ctrl, F_SDH30_IO_CONTROL2); | ||
usleep_range(2500, 3000); | ||
|
||
if (priv->vendor_hs200) { | ||
dev_info(priv->dev, "%s: setting hs200\n", __func__); | ||
ctrl = sdhci_readl(host, F_SDH30_ESD_CONTROL); | ||
ctrl |= priv->vendor_hs200; | ||
sdhci_writel(host, ctrl, F_SDH30_ESD_CONTROL); | ||
} | ||
|
||
ctrl = sdhci_readl(host, F_SDH30_TUNING_SETTING); | ||
ctrl |= F_SDH30_CMD_CHK_DIS; | ||
sdhci_writel(host, ctrl, F_SDH30_TUNING_SETTING); | ||
} | ||
|
||
unsigned int sdhci_f_sdh30_get_min_clock(struct sdhci_host *host) | ||
{ | ||
return F_SDH30_MIN_CLOCK; | ||
} | ||
|
||
void sdhci_f_sdh30_reset(struct sdhci_host *host, u8 mask) | ||
{ | ||
if (sdhci_readw(host, SDHCI_CLOCK_CONTROL) == 0) | ||
sdhci_writew(host, 0xBC01, SDHCI_CLOCK_CONTROL); | ||
|
||
sdhci_reset(host, mask); | ||
} | ||
|
||
static const struct sdhci_ops sdhci_f_sdh30_ops = { | ||
.voltage_switch = sdhci_f_sdh30_soft_voltage_switch, | ||
.get_min_clock = sdhci_f_sdh30_get_min_clock, | ||
.reset = sdhci_f_sdh30_reset, | ||
.set_clock = sdhci_set_clock, | ||
.set_bus_width = sdhci_set_bus_width, | ||
.set_uhs_signaling = sdhci_set_uhs_signaling, | ||
}; | ||
|
||
static int sdhci_f_sdh30_probe(struct platform_device *pdev) | ||
{ | ||
struct sdhci_host *host; | ||
struct device *dev = &pdev->dev; | ||
struct resource *res; | ||
int irq, ctrl = 0, ret = 0; | ||
struct f_sdhost_priv *priv; | ||
u32 reg = 0; | ||
|
||
irq = platform_get_irq(pdev, 0); | ||
if (irq < 0) { | ||
dev_err(dev, "%s: no irq specified\n", __func__); | ||
return irq; | ||
} | ||
|
||
host = sdhci_alloc_host(dev, sizeof(struct sdhci_host) + | ||
sizeof(struct f_sdhost_priv)); | ||
if (IS_ERR(host)) | ||
return PTR_ERR(host); | ||
|
||
priv = sdhci_priv(host); | ||
priv->dev = dev; | ||
|
||
host->quirks = SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC | | ||
SDHCI_QUIRK_INVERTED_WRITE_PROTECT; | ||
host->quirks2 = SDHCI_QUIRK2_SUPPORT_SINGLE | | ||
SDHCI_QUIRK2_TUNING_WORK_AROUND; | ||
|
||
ret = mmc_of_parse(host->mmc); | ||
if (ret) | ||
goto err; | ||
|
||
platform_set_drvdata(pdev, host); | ||
|
||
sdhci_get_of_property(pdev); | ||
host->hw_name = "f_sdh30"; | ||
host->ops = &sdhci_f_sdh30_ops; | ||
host->irq = irq; | ||
|
||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
host->ioaddr = devm_ioremap_resource(&pdev->dev, res); | ||
if (IS_ERR(host->ioaddr)) { | ||
ret = PTR_ERR(host); | ||
goto err; | ||
} | ||
|
||
priv->clk_iface = devm_clk_get(&pdev->dev, "iface"); | ||
if (IS_ERR(priv->clk_iface)) { | ||
ret = PTR_ERR(priv->clk_iface); | ||
goto err; | ||
} | ||
|
||
ret = clk_prepare_enable(priv->clk_iface); | ||
if (ret) | ||
goto err; | ||
|
||
priv->clk = devm_clk_get(&pdev->dev, "core"); | ||
if (IS_ERR(priv->clk)) { | ||
ret = PTR_ERR(priv->clk); | ||
goto err_clk; | ||
} | ||
|
||
ret = clk_prepare_enable(priv->clk); | ||
if (ret) | ||
goto err_clk; | ||
|
||
/* init vendor specific regs */ | ||
ctrl = sdhci_readw(host, F_SDH30_AHB_CONFIG); | ||
ctrl |= F_SDH30_SIN | F_SDH30_AHB_INCR_16 | F_SDH30_AHB_INCR_8 | | ||
F_SDH30_AHB_INCR_4; | ||
ctrl &= ~(F_SDH30_AHB_BIGED | F_SDH30_BUSLOCK_EN); | ||
sdhci_writew(host, ctrl, F_SDH30_AHB_CONFIG); | ||
|
||
reg = sdhci_readl(host, F_SDH30_ESD_CONTROL); | ||
sdhci_writel(host, reg & ~F_SDH30_EMMC_RST, F_SDH30_ESD_CONTROL); | ||
msleep(20); | ||
sdhci_writel(host, reg | F_SDH30_EMMC_RST, F_SDH30_ESD_CONTROL); | ||
|
||
reg = sdhci_readl(host, SDHCI_CAPABILITIES); | ||
if (reg & SDHCI_CAN_DO_8BIT) | ||
priv->vendor_hs200 = F_SDH30_EMMC_HS200; | ||
|
||
ret = sdhci_add_host(host); | ||
if (ret) | ||
goto err_add_host; | ||
|
||
return 0; | ||
|
||
err_add_host: | ||
clk_disable_unprepare(priv->clk); | ||
err_clk: | ||
clk_disable_unprepare(priv->clk_iface); | ||
err: | ||
sdhci_free_host(host); | ||
return ret; | ||
} | ||
|
||
static int sdhci_f_sdh30_remove(struct platform_device *pdev) | ||
{ | ||
struct sdhci_host *host = platform_get_drvdata(pdev); | ||
struct f_sdhost_priv *priv = sdhci_priv(host); | ||
|
||
sdhci_remove_host(host, readl(host->ioaddr + SDHCI_INT_STATUS) == | ||
0xffffffff); | ||
|
||
clk_disable_unprepare(priv->clk_iface); | ||
clk_disable_unprepare(priv->clk); | ||
|
||
sdhci_free_host(host); | ||
platform_set_drvdata(pdev, NULL); | ||
|
||
return 0; | ||
} | ||
|
||
static const struct of_device_id f_sdh30_dt_ids[] = { | ||
{ .compatible = "fujitsu,mb86s70-sdhci-3.0" }, | ||
{ /* sentinel */ } | ||
}; | ||
MODULE_DEVICE_TABLE(of, f_sdh30_dt_ids); | ||
|
||
static struct platform_driver sdhci_f_sdh30_driver = { | ||
.driver = { | ||
.name = "f_sdh30", | ||
.of_match_table = f_sdh30_dt_ids, | ||
.pm = SDHCI_PLTFM_PMOPS, | ||
}, | ||
.probe = sdhci_f_sdh30_probe, | ||
.remove = sdhci_f_sdh30_remove, | ||
}; | ||
|
||
module_platform_driver(sdhci_f_sdh30_driver); | ||
|
||
MODULE_DESCRIPTION("F_SDH30 SD Card Controller driver"); | ||
MODULE_LICENSE("GPL v2"); | ||
MODULE_AUTHOR("FUJITSU SEMICONDUCTOR LTD."); | ||
MODULE_ALIAS("platform:f_sdh30"); |