403 lines
9.3 KiB
C
403 lines
9.3 KiB
C
#ifdef CONFIG_ARCH_SUN8IW10P1
|
|
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/clk-private.h>
|
|
#include <linux/clk/sunxi.h>
|
|
|
|
#include <linux/gpio.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/scatterlist.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/reset.h>
|
|
|
|
#include <linux/of_address.h>
|
|
#include <linux/of_gpio.h>
|
|
#include <linux/of_platform.h>
|
|
|
|
#include <linux/mmc/host.h>
|
|
#include <linux/mmc/sd.h>
|
|
#include <linux/mmc/sdio.h>
|
|
#include <linux/mmc/mmc.h>
|
|
#include <linux/mmc/core.h>
|
|
#include <linux/mmc/card.h>
|
|
#include <linux/mmc/slot-gpio.h>
|
|
|
|
|
|
#include "sunxi-smhc.h"
|
|
#include "sunxi-mmc-sun8iw10p1-2.h"
|
|
|
|
|
|
|
|
static void sunxi_mmc_set_clk_dly(struct sunxi_mmc_host *host,int clk,int bus_width,int timing)
|
|
{
|
|
dev_dbg(mmc_dev(host->mmc),"no imple %s %d\n",__FUNCTION__,__LINE__);
|
|
}
|
|
|
|
|
|
void sunxi_mmc_dump_dly2(struct sunxi_mmc_host *host)
|
|
{
|
|
dev_dbg(mmc_dev(host->mmc),"no imple %s %d\n",__FUNCTION__,__LINE__);
|
|
}
|
|
|
|
|
|
|
|
static int __sunxi_mmc_do_oclk_onoff(struct sunxi_mmc_host *host, u32 oclk_en,u32 pwr_save,u32 ignore_dat0)
|
|
{
|
|
u32 tmp = 0;
|
|
|
|
tmp = smhc_readl(host,SMHC_RST_CLK_CTRL);
|
|
if (oclk_en) {
|
|
tmp |= SdclkEn;
|
|
} else {
|
|
tmp &= ~SdclkEn;
|
|
}
|
|
smhc_writel(host,SMHC_RST_CLK_CTRL,tmp);
|
|
|
|
tmp = smhc_readl(host,SMHC_CTRL3);
|
|
if (pwr_save) {
|
|
tmp |= SdclkIdleCtrl;
|
|
} else {
|
|
tmp &= ~SdclkIdleCtrl;
|
|
}
|
|
smhc_writel(host,SMHC_CTRL3,tmp);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
static int sunxi_mmc_oclk_onoff(struct sunxi_mmc_host *host, u32 oclk_en)
|
|
{
|
|
struct device_node *np = NULL;
|
|
struct mmc_host *mmc = host->mmc;
|
|
int pwr_save = 0;
|
|
int len = 0;
|
|
|
|
if (!mmc->parent || !mmc->parent->of_node){
|
|
dev_err(mmc_dev(host->mmc), "no dts to parse power save mode\n");
|
|
return -EIO; ;
|
|
}
|
|
|
|
np = mmc->parent->of_node;
|
|
if (of_find_property(np, "sunxi-power-save-mode", &len))
|
|
pwr_save = 1;
|
|
return __sunxi_mmc_do_oclk_onoff(host,oclk_en,pwr_save,1);
|
|
}
|
|
int sunxi_mmc_clk_set_rate_for_sdmmc2(struct sunxi_mmc_host *host,
|
|
struct mmc_ios *ios)
|
|
{
|
|
u32 mod_clk = 0;
|
|
u32 src_clk = 0;
|
|
u32 rval = 0;
|
|
s32 err = 0;
|
|
u32 rate = 0;
|
|
char *sclk_name = NULL;
|
|
struct clk *mclk = host->clk_mmc;
|
|
struct clk *sclk = NULL;
|
|
struct device *dev = mmc_dev(host->mmc);
|
|
|
|
if(ios->clock == 0){
|
|
__sunxi_mmc_do_oclk_onoff(host, 0,0,1);
|
|
return 0;
|
|
}
|
|
|
|
if(ios->timing == MMC_TIMING_UHS_DDR50){
|
|
mod_clk = ios->clock<<3;
|
|
}else{
|
|
mod_clk = ios->clock<<2;
|
|
}
|
|
|
|
if (ios->clock<= 400000) {
|
|
//sclk = of_clk_get(np, 0);
|
|
sclk = clk_get(dev,"osc24m");
|
|
sclk_name = "osc24m";
|
|
} else {
|
|
//sclk = clk_get(np, 1);
|
|
sclk = clk_get(dev,"pll_periph");
|
|
sclk_name = "pll_periph";
|
|
}
|
|
if (IS_ERR(sclk)) {
|
|
dev_err(mmc_dev(host->mmc), "Error to get source clock %s\n",sclk_name);
|
|
return -1;
|
|
}
|
|
|
|
sunxi_mmc_oclk_onoff(host, 0);
|
|
|
|
err = clk_set_parent(mclk, sclk);
|
|
if(err){
|
|
dev_err(mmc_dev(host->mmc), "set parent failed\n");
|
|
clk_put(sclk);
|
|
return -1;
|
|
}
|
|
|
|
rate = clk_round_rate(mclk, mod_clk);
|
|
|
|
dev_dbg(mmc_dev(host->mmc),"get round rate %d\n", rate);
|
|
|
|
clk_disable_unprepare(host->clk_mmc);
|
|
|
|
err = clk_set_rate(mclk, rate);
|
|
if (err) {
|
|
dev_err(mmc_dev(host->mmc),"set mclk rate error, rate %dHz\n",rate);
|
|
clk_put(sclk);
|
|
return -1;
|
|
}
|
|
|
|
rval = clk_prepare_enable(host->clk_mmc);
|
|
if (rval) {
|
|
dev_err(mmc_dev(host->mmc), "Enable mmc clk err %d\n", rval);
|
|
return -1;
|
|
}
|
|
|
|
src_clk = clk_get_rate(sclk);
|
|
clk_put(sclk);
|
|
|
|
dev_dbg(mmc_dev(host->mmc),"set round clock %d, soure clk is %d\n", rate, src_clk);
|
|
|
|
//sunxi_of_parse_clk_dly(host);
|
|
if(ios->timing == MMC_TIMING_UHS_DDR50){
|
|
ios->clock = rate>>3;
|
|
}else{
|
|
ios->clock = rate>>2;
|
|
}
|
|
|
|
sunxi_mmc_set_clk_dly(host,ios->clock,ios->bus_width,ios->timing);
|
|
|
|
return sunxi_mmc_oclk_onoff(host, 1);
|
|
}
|
|
|
|
void sunxi_mmc_thld_ctl_for_sdmmc2(struct sunxi_mmc_host *host,
|
|
struct mmc_ios *ios, struct mmc_data *data)
|
|
{
|
|
dev_dbg(mmc_dev(host->mmc),"no imple %s %d\n",__FUNCTION__,__LINE__);
|
|
}
|
|
|
|
|
|
void sunxi_mmc_save_spec_reg2(struct sunxi_mmc_host *host)
|
|
{
|
|
dev_dbg(mmc_dev(host->mmc),"no imple %s %d\n",__FUNCTION__,__LINE__);
|
|
|
|
}
|
|
|
|
void sunxi_mmc_restore_spec_reg2(struct sunxi_mmc_host *host)
|
|
{
|
|
dev_dbg(mmc_dev(host->mmc),"no imple %s %d\n",__FUNCTION__,__LINE__);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
extern int mmc_go_idle(struct mmc_host *host);
|
|
extern int mmc_send_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr);
|
|
extern int mmc_send_status(struct mmc_card *card, u32 *status);
|
|
extern void mmc_set_clock(struct mmc_host *host, unsigned int hz);
|
|
extern void mmc_set_timing(struct mmc_host *host, unsigned int timing);
|
|
extern void mmc_set_bus_width(struct mmc_host *host, unsigned int width);
|
|
void sunxi_mmc_do_shutdown2(struct platform_device * pdev)
|
|
{
|
|
u32 ocr = 0;
|
|
u32 err = 0;
|
|
struct mmc_host *mmc = NULL;
|
|
struct sunxi_mmc_host *host = NULL;
|
|
u32 status = 0;
|
|
|
|
mmc = platform_get_drvdata(pdev);
|
|
if (mmc == NULL) {
|
|
dev_err(&pdev->dev,"%s: mmc is NULL\n", __FUNCTION__);
|
|
goto out;
|
|
}
|
|
|
|
host = mmc_priv(mmc);
|
|
if (host == NULL) {
|
|
dev_err(&pdev->dev,"%s: host is NULL\n", __FUNCTION__);
|
|
goto out;
|
|
}
|
|
|
|
dev_info(mmc_dev(mmc),"try to disable cache\n");
|
|
mmc_claim_host(mmc);
|
|
err = mmc_cache_ctrl(mmc, 0);
|
|
mmc_release_host(mmc);
|
|
if (err){
|
|
dev_err(mmc_dev(mmc),"disable cache failed\n");
|
|
mmc_claim_host(mmc);//not release host to not allow android to read/write after shutdown
|
|
goto out;
|
|
}
|
|
|
|
//claim host to not allow androd read/write during shutdown
|
|
dev_dbg(mmc_dev(mmc),"%s: claim host\n", __FUNCTION__);
|
|
mmc_claim_host(mmc);
|
|
|
|
do {
|
|
if (mmc_send_status(mmc->card, &status) != 0) {
|
|
dev_err(mmc_dev(mmc),"%s: send status failed\n", __FUNCTION__);
|
|
goto out; //err_out; //not release host to not allow android to read/write after shutdown
|
|
}
|
|
} while(status != 0x00000900);
|
|
|
|
//mmc_card_set_ddr_mode(card);
|
|
mmc_set_timing(mmc, MMC_TIMING_LEGACY);
|
|
mmc_set_bus_width(mmc, MMC_BUS_WIDTH_1);
|
|
mmc_set_clock(mmc, 400000);
|
|
err = mmc_go_idle(mmc);
|
|
if (err) {
|
|
dev_err(mmc_dev(mmc),"%s: mmc_go_idle err\n", __FUNCTION__);
|
|
goto out; //err_out; //not release host to not allow android to read/write after shutdown
|
|
}
|
|
|
|
if (mmc->card->type != MMC_TYPE_MMC) {//sd can support cmd1,so not send cmd1
|
|
goto out;//not release host to not allow android to read/write after shutdown
|
|
}
|
|
|
|
err = mmc_send_op_cond(mmc, 0, &ocr);
|
|
if (err) {
|
|
dev_err(mmc_dev(mmc),"%s: first mmc_send_op_cond err\n", __FUNCTION__);
|
|
goto out; //err_out; //not release host to not allow android to read/write after shutdown
|
|
}
|
|
|
|
err = mmc_send_op_cond(mmc, ocr | (1 << 30), &ocr);
|
|
if (err) {
|
|
dev_err(mmc_dev(mmc),"%s: mmc_send_op_cond err\n", __FUNCTION__);
|
|
goto out; //err_out; //not release host to not allow android to read/write after shutdown
|
|
}
|
|
|
|
//do not release host to not allow android to read/write after shutdown
|
|
goto out;
|
|
|
|
out:
|
|
dev_info(mmc_dev(mmc),"%s: mmc shutdown exit..ok\n", __FUNCTION__);
|
|
|
|
return ;
|
|
}
|
|
*/
|
|
|
|
int mmc_card_sleep(struct mmc_host *host);
|
|
int mmc_deselect_cards(struct mmc_host *host);
|
|
void mmc_power_off(struct mmc_host *host);
|
|
int mmc_card_sleepawake(struct mmc_host *host, int sleep);
|
|
|
|
|
|
static int sunxi_mmc_can_poweroff_notify(const struct mmc_card *card)
|
|
{
|
|
return card &&
|
|
mmc_card_mmc(card) &&
|
|
(card->ext_csd.power_off_notification == EXT_CSD_POWER_ON);
|
|
}
|
|
|
|
|
|
static int sunxi_mmc_poweroff_notify(struct mmc_card *card, unsigned int notify_type)
|
|
{
|
|
unsigned int timeout = card->ext_csd.generic_cmd6_time;
|
|
int err;
|
|
|
|
/* Use EXT_CSD_POWER_OFF_SHORT as default notification type. */
|
|
if (notify_type == EXT_CSD_POWER_OFF_LONG)
|
|
timeout = card->ext_csd.power_off_longtime;
|
|
|
|
err = __mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
|
|
EXT_CSD_POWER_OFF_NOTIFICATION,
|
|
notify_type, timeout, true, false, false);
|
|
if (err)
|
|
pr_err("%s: Power Off Notification timed out, %u\n",
|
|
mmc_hostname(card->host), timeout);
|
|
|
|
/* Disable the power off notification after the switch operation. */
|
|
card->ext_csd.power_off_notification = EXT_CSD_NO_POWER_NOTIFICATION;
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
static int sunxi_mmc_sleep(struct mmc_host *host)
|
|
{
|
|
struct mmc_card *card = host->card;
|
|
int err = -ENOSYS;
|
|
|
|
if (card && card->ext_csd.rev >= 3) {
|
|
err = mmc_card_sleepawake(host, 1);
|
|
if (err < 0)
|
|
pr_debug("%s: Error %d while putting card into sleep",
|
|
mmc_hostname(host), err);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
|
|
static int sunxi_mmc_suspend(struct mmc_host *host, bool is_suspend)
|
|
{
|
|
int err = 0;
|
|
unsigned int notify_type = is_suspend ? EXT_CSD_POWER_OFF_SHORT :
|
|
EXT_CSD_POWER_OFF_LONG;
|
|
|
|
BUG_ON(!host);
|
|
BUG_ON(!host->card);
|
|
|
|
mmc_claim_host(host);
|
|
|
|
//if (mmc_card_suspended(host->card))
|
|
// goto out;
|
|
|
|
if (mmc_card_doing_bkops(host->card)) {
|
|
err = mmc_stop_bkops(host->card);
|
|
if (err)
|
|
goto out;
|
|
}
|
|
|
|
err = mmc_flush_cache(host->card);
|
|
|
|
if (err)
|
|
goto out;
|
|
|
|
if (sunxi_mmc_can_poweroff_notify(host->card) &&
|
|
((host->caps2 & MMC_CAP2_POWEROFF_NOTIFY) || !is_suspend)){
|
|
err = sunxi_mmc_poweroff_notify(host->card, notify_type);
|
|
}else if (mmc_card_can_sleep(host)){
|
|
err = sunxi_mmc_sleep(host);
|
|
}else if (!mmc_host_is_spi(host)){
|
|
err = mmc_deselect_cards(host);
|
|
}
|
|
|
|
if (!err) {
|
|
pr_info("%s: %s %d\n",
|
|
mmc_hostname(host),__FUNCTION__,__LINE__);
|
|
mmc_power_off(host);
|
|
// mmc_card_set_suspended(host->card);
|
|
}
|
|
|
|
out:
|
|
mmc_release_host(host);
|
|
return err;
|
|
}
|
|
|
|
|
|
|
|
void sunxi_mmc_do_shutdown2(struct platform_device * pdev)
|
|
{
|
|
struct mmc_host *mmc = platform_get_drvdata(pdev);
|
|
u32 shutdown_notify_type = 0;
|
|
u32 rval = of_property_read_u32(mmc->parent->of_node, "shutdown_notify_type", &shutdown_notify_type);
|
|
if(!rval){
|
|
sunxi_mmc_suspend(mmc ,shutdown_notify_type);
|
|
}else{
|
|
sunxi_mmc_suspend(mmc ,false);
|
|
}
|
|
}
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|