mirror of https://github.com/OpenIPC/firmware.git
				
				
				
			
		
			
				
	
	
		
			805 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			Diff
		
	
	
			
		
		
	
	
			805 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			Diff
		
	
	
| diff -drupN a/drivers/mmc/host/sdhci-ingenic.c b/drivers/mmc/host/sdhci-ingenic.c
 | |
| --- a/drivers/mmc/host/sdhci-ingenic.c	1970-01-01 03:00:00.000000000 +0300
 | |
| +++ b/drivers/mmc/host/sdhci-ingenic.c	2022-06-09 05:02:30.000000000 +0300
 | |
| @@ -0,0 +1,800 @@
 | |
| +#include <linux/delay.h>
 | |
| +#include <linux/dma-mapping.h>
 | |
| +#include <linux/platform_device.h>
 | |
| +#include <linux/slab.h>
 | |
| +#include <linux/clk.h>
 | |
| +#include <linux/io.h>
 | |
| +#include <linux/gpio.h>
 | |
| +#include <linux/module.h>
 | |
| +#include <linux/of.h>
 | |
| +#include <linux/of_gpio.h>
 | |
| +#include <linux/of_address.h>
 | |
| +#include <linux/pm.h>
 | |
| +#include <linux/pm_runtime.h>
 | |
| +
 | |
| +#include <linux/mmc/host.h>
 | |
| +#include <soc/cpm.h>
 | |
| +#include <soc/base.h>
 | |
| +
 | |
| +#include "sdhci.h"
 | |
| +#include "sdhci-ingenic.h"
 | |
| +
 | |
| +#define CLK_CTRL
 | |
| +/* Software redefinition caps */
 | |
| +#define CAPABILITIES1_SW	0x276dc898
 | |
| +#define CAPABILITIES2_SW	0
 | |
| +
 | |
| +static LIST_HEAD(manual_list);
 | |
| +#define CPM_MSC0_CLK_R		(0xB0000068)
 | |
| +#define CPM_MSC1_CLK_R		(0xB000006C)
 | |
| +#ifdef CONFIG_FPGA_TEST
 | |
| +#define MSC_CLK_H_FREQ		(0x1 << 20)
 | |
| +
 | |
| +static void sdhci_ingenic_fpga_clk(unsigned int clock)
 | |
| +{
 | |
| +#define CPM_MSC_CLK_R CPM_MSC0_CLK_R
 | |
| +//#define CPM_MSC_CLK_R CPM_MSC1_CLK_R
 | |
| +	unsigned int val;
 | |
| +
 | |
| +	if(500000 <= clock){
 | |
| +		val = readl((const volatile void*)CPM_MSC_CLK_R);
 | |
| +		val |= MSC_CLK_H_FREQ;
 | |
| +		writel(val, (void*)CPM_MSC_CLK_R);
 | |
| +	} else {
 | |
| +		val = readl((const volatile void*)CPM_MSC_CLK_R);
 | |
| +		val &= ~MSC_CLK_H_FREQ;
 | |
| +		writel(val, (void*)CPM_MSC_CLK_R);
 | |
| +	}
 | |
| +	//printk("\tclk=%d, CPM_MSC0_CLK_R: %08x\n\n", clock, readl((const volatile void*)CPM_MSC0_CLK_R));
 | |
| +}
 | |
| +#endif
 | |
| +
 | |
| +static unsigned int sdhci_ingenic_get_cpm_msc(struct sdhci_host *host)
 | |
| +{
 | |
| +	char msc_ioaddr[16];
 | |
| +	unsigned int cpm_msc;
 | |
| +	sprintf(msc_ioaddr, "0x%x", (unsigned int)host->ioaddr);
 | |
| +
 | |
| +	if (!strcmp(msc_ioaddr ,"0xb3060000"))
 | |
| +		cpm_msc = CPM_MSC0_CLK_R;
 | |
| +	if (!strcmp(msc_ioaddr ,"0xb3070000"))
 | |
| +		cpm_msc = CPM_MSC1_CLK_R;
 | |
| +	return cpm_msc;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * sdhci_ingenic_msc_tuning  Enable msc controller tuning
 | |
| + *
 | |
| + * Tuning rx phase
 | |
| + * */
 | |
| +static void sdhci_ingenic_en_msc_tuning(struct sdhci_host *host, unsigned int cpm_msc)
 | |
| +{
 | |
| +	if (host->flags & SDHCI_SDR50_NEEDS_TUNING ||
 | |
| +		host->flags & SDHCI_SDR104_NEEDS_TUNING ||
 | |
| +		host->flags & SDHCI_HS400_TUNING) {
 | |
| +		*(volatile unsigned int*)cpm_msc &= ~(0x1 << 20);
 | |
| +	}
 | |
| +}
 | |
| +
 | |
| +static void sdhci_ingenic_sel_rx_phase(unsigned int cpm_msc)
 | |
| +{
 | |
| +	*(volatile unsigned int*)cpm_msc |= (0x1 << 20); // default
 | |
| +
 | |
| +	*(volatile unsigned int*)cpm_msc &= ~(0x7 << 17);
 | |
| +	*(volatile unsigned int*)cpm_msc |= (0x7 << 17); // OK  RX 90 TX 270
 | |
| +}
 | |
| +
 | |
| +static void sdhci_ingenic_sel_tx_phase(unsigned int cpm_msc)
 | |
| +{
 | |
| +	*(volatile unsigned int*)cpm_msc &= ~(0x3 << 15);
 | |
| +/*	*(volatile unsigned int*)cpm_msc |= (0x2 << 15); // 180  100M OK*/
 | |
| +	*(volatile unsigned int*)cpm_msc |= (0x3 << 15);
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * sdhci_ingenic_set_clock - callback on clock change
 | |
| + * @host: The SDHCI host being changed
 | |
| + * @clock: The clock rate being requested.
 | |
| + *
 | |
| + * When the card's clock is going to be changed, look at the new frequency
 | |
| + * and find the best clock source to go with it.
 | |
| +*/
 | |
| +static void sdhci_ingenic_set_clock(struct sdhci_host *host, unsigned int clock)
 | |
| +{
 | |
| +#ifndef CONFIG_FPGA_TEST
 | |
| +	struct sdhci_ingenic *sdhci_ing = sdhci_priv(host);
 | |
| +	unsigned int cpm_msc = sdhci_ingenic_get_cpm_msc(host);
 | |
| +	char clkname[16];
 | |
| +
 | |
| +
 | |
| +	if (clock == 0)
 | |
| +		return;
 | |
| +
 | |
| +	sdhci_set_clock(host, clock);
 | |
| +
 | |
| +	spin_unlock_irq(&host->lock);
 | |
| +	sprintf(clkname, "mux_msc%d", sdhci_ing->pdev->id);
 | |
| +	sdhci_ing->clk_mux = clk_get(NULL, clkname);
 | |
| +
 | |
| +	if (clock > 400000) {
 | |
| +		clk_set_parent(sdhci_ing->clk_mux, sdhci_ing->clk_mpll);
 | |
| +	} else {
 | |
| +		clk_set_parent(sdhci_ing->clk_mux, sdhci_ing->clk_ext);
 | |
| +		*(volatile unsigned int *)0xB0000068 |= 1 << 21;
 | |
| +	}
 | |
| +
 | |
| +	clk_set_rate(sdhci_ing->clk_cgu, clock);
 | |
| +
 | |
| +	spin_lock_irq(&host->lock);
 | |
| +	//printk("%s, set clk: %d, get_clk_rate=%ld\n", __func__, clock, clk_get_rate(sdhci_ing->clk_cgu));
 | |
| +
 | |
| +	if (host->mmc->ios.timing == MMC_TIMING_MMC_HS200 ||
 | |
| +		host->mmc->ios.timing == MMC_TIMING_UHS_SDR104) {
 | |
| +
 | |
| +		/* RX phase selecte */
 | |
| +		if (sdhci_ing->pdata->enable_cpm_rx_tuning == 1)
 | |
| +			sdhci_ingenic_sel_rx_phase(cpm_msc);
 | |
| +		else
 | |
| +			sdhci_ingenic_en_msc_tuning(host, cpm_msc);
 | |
| +		/* TX phase selecte */
 | |
| +		if (sdhci_ing->pdata->enable_cpm_tx_tuning == 1)
 | |
| +			sdhci_ingenic_sel_tx_phase(cpm_msc);
 | |
| +	}
 | |
| +#else //CONFIG_FPGA_TEST
 | |
| +	sdhci_ingenic_fpga_clk(clock);
 | |
| +#endif
 | |
| +}
 | |
| +
 | |
| +#ifdef VOLTAGE_SWITCH
 | |
| +static void sdhci_ingenic_set_cpm_poc(int power_v18)
 | |
| +{
 | |
| +	if (power_v18) {
 | |
| +		cpm_outl(0x8, CPM_POC);
 | |
| +	} else {
 | |
| +		cpm_outl(0x0, CPM_POC);
 | |
| +	}
 | |
| +
 | |
| +	return;
 | |
| +}
 | |
| +#endif
 | |
| +
 | |
| +/* I/O Driver Strength Types */
 | |
| +#define INGENIC_TYPE_0  0x0		//30
 | |
| +#define INGENIC_TYPE_1  0x1		//50
 | |
| +#define INGENIC_TYPE_2  0x2		//66
 | |
| +#define INGENIC_TYPE_3  0x3		//100
 | |
| +
 | |
| +static int sdhci_ingenic_select_drive_strength(struct sdhci_host *host,
 | |
| +		struct mmc_card *card,
 | |
| +		unsigned int max_dtr,
 | |
| +		int host_drv, int card_drv, int *drv_type)
 | |
| +{
 | |
| +	int drive_strength, type;
 | |
| +
 | |
| +	type = INGENIC_TYPE_1;
 | |
| +
 | |
| +	switch (type) {
 | |
| +	case INGENIC_TYPE_0:
 | |
| +		/* Type 0: 50 */
 | |
| +		*(volatile unsigned int*)(0xb0010500 + 0xb8) |= 0xfff;
 | |
| +		*(volatile unsigned int*)(0xb0010500 + 0xb4) |= 0x555;
 | |
| +		drive_strength = 0;
 | |
| +		break;
 | |
| +	case INGENIC_TYPE_1:
 | |
| +		/* Type 1: 33 */
 | |
| +		*(volatile unsigned int*)(0xb0010500 + 0xb8) |= 0xfff;
 | |
| +		*drv_type = MMC_CAP_DRIVER_TYPE_A; // GPIO DRV_STR
 | |
| +		drive_strength = 1;
 | |
| +		break;
 | |
| +	case INGENIC_TYPE_2:
 | |
| +		/* Type 2: 66 */
 | |
| +		*(volatile unsigned int*)(0xb0010500 + 0xb8) |= 0xfff;
 | |
| +		*(volatile unsigned int*)(0xb0010500 + 0xb4) |= 0xaaa;
 | |
| +		*drv_type = MMC_CAP_DRIVER_TYPE_C; // GPIO DRV_STR
 | |
| +		drive_strength = 2;
 | |
| +		break;
 | |
| +	case INGENIC_TYPE_3:
 | |
| +		/* Type 3: 100 */
 | |
| +		*(volatile unsigned int*)(0xb0010500 + 0xb8) |= 0xfff;
 | |
| +		*(volatile unsigned int*)(0xb0010500 + 0xb4) |= 0xfff;
 | |
| +		*drv_type = MMC_CAP_DRIVER_TYPE_D; // GPIO DRV_STR
 | |
| +		drive_strength = 3;
 | |
| +		break;
 | |
| +	}
 | |
| +
 | |
| +	return drive_strength;
 | |
| +}
 | |
| +
 | |
| +#ifdef VOLTAGE_SWITCH
 | |
| +void sdhci_ingenic_voltage_switch(struct sdhci_host *host)
 | |
| +{
 | |
| +	struct sdhci_ingenic *sdhci_ing = sdhci_priv(host);
 | |
| +	struct sdhci_ingenic_pdata *pdata = sdhci_ing->pdata;
 | |
| +	unsigned int val;
 | |
| +
 | |
| +	if (pdata->sdr_v18 < 0)
 | |
| +		return;
 | |
| +
 | |
| +	/*controlled SD voltage to 1.8V*/
 | |
| +	val = cpm_inl(CPM_EXCLK_DS) | (1 << 31);
 | |
| +	cpm_outl(val, CPM_EXCLK_DS);
 | |
| +
 | |
| +	/*Set up hardware circuit 1.8V*/
 | |
| +	val = pdata->sdr_v18;
 | |
| +
 | |
| +	gpio_direction_output(val, 1);
 | |
| +}
 | |
| +#endif
 | |
| +
 | |
| +static struct sdhci_ops sdhci_ingenic_ops = {
 | |
| +	.set_clock							  = sdhci_ingenic_set_clock,
 | |
| +	.set_bus_width						  = sdhci_set_bus_width,
 | |
| +	.reset								  = sdhci_reset,
 | |
| +	.set_uhs_signaling					  = sdhci_set_uhs_signaling,
 | |
| +	.select_drive_strength				  = sdhci_ingenic_select_drive_strength,
 | |
| +#ifdef VOLTAGE_SWITCH
 | |
| +	.voltage_switch						  = sdhci_ingenic_voltage_switch,
 | |
| +#endif
 | |
| +};
 | |
| +
 | |
| +static void sdhci_ingenic_notify_change(struct platform_device *dev, int state)
 | |
| +{
 | |
| +	struct sdhci_host *host = platform_get_drvdata(dev);
 | |
| +	struct sdhci_ingenic *sdhci_ing = sdhci_priv(host);
 | |
| +	struct sdhci_ingenic_pdata *pdata = sdhci_ing->pdata;
 | |
| +	unsigned long flags, val;
 | |
| +
 | |
| +	if (host) {
 | |
| +		spin_lock_irqsave(&host->lock, flags);
 | |
| +		if (state) {
 | |
| +			printk("%s: card inserted\n", mmc_hostname(host->mmc));
 | |
| +			host->flags &= ~SDHCI_DEVICE_DEAD;
 | |
| +			host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION;
 | |
| +
 | |
| +			mmc_detect_change(host->mmc, 0);
 | |
| +		} else {
 | |
| +			printk("%s: card removed\n", mmc_hostname(host->mmc));
 | |
| +			host->flags |= SDHCI_DEVICE_DEAD;
 | |
| +			host->quirks &= ~SDHCI_QUIRK_BROKEN_CARD_DETECTION;
 | |
| +#ifdef VOLTAGE_SWITCH
 | |
| +			if (pdata->sdr_v18 > 0) {
 | |
| +				gpio_direction_output(pdata->sdr_v18, 0);
 | |
| +
 | |
| +				val = cpm_inl(CPM_EXCLK_DS) & ~(1 << 31);
 | |
| +				cpm_outl(val, CPM_EXCLK_DS);
 | |
| +			}
 | |
| +#endif
 | |
| +			mmc_detect_change(host->mmc, 0);
 | |
| +		}
 | |
| +		tasklet_schedule(&host->finish_tasklet);
 | |
| +		spin_unlock_irqrestore(&host->lock, flags);
 | |
| +	}
 | |
| +}
 | |
| +
 | |
| +static irqreturn_t sdhci_ingenic_gpio_card_detect_thread(int irq, void *dev_id)
 | |
| +{
 | |
| +	struct sdhci_ingenic *sdhci_ing = (struct sdhci_ingenic *)dev_id;
 | |
| +	int status = gpio_get_value(sdhci_ing->ext_cd_gpio);
 | |
| +
 | |
| +	if (!(sdhci_ing->pdata->gpio->cd.enable_level))
 | |
| +		status = !status;
 | |
| +	sdhci_ingenic_notify_change(sdhci_ing->pdev, status);
 | |
| +	return IRQ_HANDLED;
 | |
| +}
 | |
| +
 | |
| +static void sdhci_ingenic_setup_card_detect_gpio(struct sdhci_ingenic *sdhci_ing)
 | |
| +{
 | |
| +	struct sdhci_ingenic_pdata *pdata = sdhci_ing->pdata;
 | |
| +	struct device *dev = &sdhci_ing->pdev->dev;
 | |
| +
 | |
| +	if (devm_gpio_request(dev, pdata->gpio->cd.num, "SDHCI EXT CD") == 0) {
 | |
| +		sdhci_ing->ext_cd_gpio = pdata->gpio->cd.num;
 | |
| +		sdhci_ing->ext_cd_irq = gpio_to_irq(pdata->gpio->cd.num);
 | |
| +		if (sdhci_ing->ext_cd_irq &&
 | |
| +		    request_threaded_irq(sdhci_ing->ext_cd_irq, NULL,
 | |
| +								 sdhci_ingenic_gpio_card_detect_thread,
 | |
| +								 IRQF_TRIGGER_RISING |
 | |
| +								 IRQF_TRIGGER_FALLING |
 | |
| +								 IRQF_ONESHOT,
 | |
| +								 dev_name(dev), sdhci_ing) == 0) {
 | |
| +			int status = gpio_get_value(sdhci_ing->ext_cd_gpio);
 | |
| +			if (!(pdata->gpio->cd.enable_level))
 | |
| +				status = !status;
 | |
| +			sdhci_ingenic_notify_change(sdhci_ing->pdev, status);
 | |
| +		} else {
 | |
| +			dev_warn(dev, "cannot request irq for card detect\n");
 | |
| +			sdhci_ing->ext_cd_irq = 0;
 | |
| +		}
 | |
| +	} else {
 | |
| +		dev_err(dev, "cannot request gpio for card detect\n");
 | |
| +	}
 | |
| +}
 | |
| +
 | |
| +#ifdef CONFIG_OF
 | |
| +static void ingenic_mmc_get_gpio(struct device_node *np,
 | |
| +								 struct ingenic_mmc_pin *pin, char *gpioname)
 | |
| +{
 | |
| +	int gpio;
 | |
| +	enum of_gpio_flags flags;
 | |
| +
 | |
| +	pin->num = -EBUSY;
 | |
| +	gpio = of_get_named_gpio_flags(np, gpioname, 0, &flags);
 | |
| +	if(gpio_is_valid(gpio)) {
 | |
| +		pin->num = gpio;
 | |
| +		pin->enable_level =
 | |
| +			(flags == OF_GPIO_ACTIVE_LOW ? LOW_ENABLE : HIGH_ENABLE);
 | |
| +		//printk("mmc gpio %s num:%d en-level: %d\n", gpioname, pin->num, pin->enable_level);
 | |
| +	}
 | |
| +}
 | |
| +
 | |
| +static inline void ingenic_mmc_clk_onoff(struct sdhci_ingenic *ingenic_ing, unsigned int on)
 | |
| +{
 | |
| +	if(on) {
 | |
| +		clk_prepare_enable(ingenic_ing->clk_cgu);
 | |
| +		clk_prepare_enable(ingenic_ing->clk_gate);
 | |
| +	} else {
 | |
| +		clk_disable_unprepare(ingenic_ing->clk_cgu);
 | |
| +		clk_disable_unprepare(ingenic_ing->clk_gate);
 | |
| +	}
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + *	jzmmc_manual_detect - insert or remove card manually
 | |
| + *	@index: host->index, namely the index of the controller.
 | |
| + *	@on: 1 means insert card, 0 means remove card.
 | |
| + *
 | |
| + *	This functions will be called by manually card-detect driver such as
 | |
| + *	wifi. To enable this mode you can set value pdata.removal = MANUAL.
 | |
| + */
 | |
| +int jzmmc_manual_detect(int index, int on)
 | |
| +{
 | |
| +	struct sdhci_ingenic *sdhci_ing;
 | |
| +	struct sdhci_host *host;
 | |
| +	struct list_head *pos;
 | |
| +
 | |
| +	list_for_each(pos, &manual_list) {
 | |
| +		sdhci_ing = list_entry(pos, struct sdhci_ingenic, list);
 | |
| +		if (sdhci_ing->pdev->id == index) {
 | |
| +			break;
 | |
| +		} else
 | |
| +			sdhci_ing = NULL;
 | |
| +	}
 | |
| +
 | |
| +	if(!sdhci_ing) {
 | |
| +		printk("no manual card detect\n");
 | |
| +		return -1;
 | |
| +	}
 | |
| +
 | |
| +	host = sdhci_ing->host;
 | |
| +
 | |
| +	if (on) {
 | |
| +		dev_err(&sdhci_ing->pdev->dev, "card insert manually\n");
 | |
| +		set_bit(INGENIC_MMC_CARD_PRESENT, &sdhci_ing->flags);
 | |
| +#ifdef CLK_CTRL
 | |
| +		ingenic_mmc_clk_onoff(sdhci_ing, 1);
 | |
| +#endif
 | |
| +		host->flags &= ~SDHCI_DEVICE_DEAD;
 | |
| +		host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION;
 | |
| +		mmc_detect_change(sdhci_ing->host->mmc, 0);
 | |
| +	} else {
 | |
| +		dev_err(&sdhci_ing->pdev->dev, "card remove manually\n");
 | |
| +		clear_bit(INGENIC_MMC_CARD_PRESENT, &sdhci_ing->flags);
 | |
| +
 | |
| +		host->flags |= SDHCI_DEVICE_DEAD;
 | |
| +		host->quirks &= ~SDHCI_QUIRK_BROKEN_CARD_DETECTION;
 | |
| +		mmc_detect_change(sdhci_ing->host->mmc, 0);
 | |
| +#ifdef CLK_CTRL
 | |
| +		ingenic_mmc_clk_onoff(sdhci_ing, 0);
 | |
| +#endif
 | |
| +	}
 | |
| +
 | |
| +	tasklet_schedule(&host->finish_tasklet);
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +EXPORT_SYMBOL(jzmmc_manual_detect);
 | |
| +
 | |
| +/**
 | |
| + *	ingenic_mmc_clk_ctrl - enable or disable msc clock gate
 | |
| + *	@index: host->index, namely the index of the controller.
 | |
| + *	@on: 1-enable msc clock gate, 0-disable msc clock gate.
 | |
| + */
 | |
| +int ingenic_mmc_clk_ctrl(int index, int on)
 | |
| +{
 | |
| +	struct sdhci_ingenic *sdhci_ing;
 | |
| +	struct list_head *pos;
 | |
| +
 | |
| +#ifdef CLK_CTRL
 | |
| +	list_for_each(pos, &manual_list) {
 | |
| +		sdhci_ing = list_entry(pos, struct sdhci_ingenic, list);
 | |
| +		if (sdhci_ing->pdev->id == index)
 | |
| +			break;
 | |
| +		else
 | |
| +			sdhci_ing = NULL;
 | |
| +	}
 | |
| +
 | |
| +	if (!sdhci_ing) {
 | |
| +		printk("no manual card detect\n");
 | |
| +		return -1;
 | |
| +	}
 | |
| +	ingenic_mmc_clk_onoff(sdhci_ing, on);
 | |
| +#endif
 | |
| +	return 0;
 | |
| +}
 | |
| +EXPORT_SYMBOL(ingenic_mmc_clk_ctrl);
 | |
| +
 | |
| +static int sdhci_ingenic_parse_dt(struct device *dev,
 | |
| +								  struct sdhci_host *host,
 | |
| +								  struct sdhci_ingenic_pdata *pdata)
 | |
| +{
 | |
| +	struct device_node *np = dev->of_node;
 | |
| +	struct card_gpio *card_gpio;
 | |
| +	unsigned int val;
 | |
| +
 | |
| +	card_gpio = devm_kzalloc(dev, sizeof(struct card_gpio), GFP_KERNEL);
 | |
| +	if(!card_gpio)
 | |
| +		return 0;
 | |
| +
 | |
| +	ingenic_mmc_get_gpio(np, &card_gpio->rst, "ingenic,rst-gpios");
 | |
| +	ingenic_mmc_get_gpio(np, &card_gpio->wp, "ingenic,wp-gpios");
 | |
| +	ingenic_mmc_get_gpio(np, &card_gpio->pwr, "ingenic,pwr-gpios");
 | |
| +	ingenic_mmc_get_gpio(np, &card_gpio->cd, "ingenic,cd-gpios");
 | |
| +	pdata->gpio = card_gpio;
 | |
| +
 | |
| +
 | |
| +	pdata->sdr_v18 = of_get_named_gpio(np, "ingenic,sdr-gpios", 0);
 | |
| +
 | |
| +	/* assuming internal card detect that will be configured by pinctrl */
 | |
| +	pdata->cd_type = SDHCI_INGENIC_CD_INTERNAL;
 | |
| +
 | |
| +	if(of_property_read_bool(np, "pio-mode")) {
 | |
| +		pdata->pio_mode = 1;
 | |
| +	}
 | |
| +	if(of_property_read_bool(np, "enable_autocmd12")) {
 | |
| +		pdata->enable_autocmd12 = 1;
 | |
| +	}
 | |
| +	if(of_property_read_bool(np, "enable_cpm_rx_tuning")) {
 | |
| +		pdata->enable_cpm_rx_tuning = 1;
 | |
| +	}
 | |
| +	if(of_property_read_bool(np, "enable_cpm_tx_tuning")) {
 | |
| +		pdata->enable_cpm_tx_tuning = 1;
 | |
| +	}
 | |
| +
 | |
| +	/* get the card detection method */
 | |
| +	if (of_get_property(np, "broken-cd", NULL)) {
 | |
| +		pdata->cd_type = SDHCI_INGENIC_CD_NONE;
 | |
| +	}
 | |
| +
 | |
| +	if (of_get_property(np, "non-removable", NULL)) {
 | |
| +		pdata->cd_type = SDHCI_INGENIC_CD_PERMANENT;
 | |
| +	}
 | |
| +
 | |
| +	if (of_get_property(np, "cd-inverted", NULL)) {
 | |
| +		pdata->cd_type = SDHCI_INGENIC_CD_GPIO;
 | |
| +	}
 | |
| +
 | |
| +	if(!(of_property_read_u32(np, "ingenic,sdio_clk", &val)))
 | |
| +		pdata->sdio_clk = val;
 | |
| +
 | |
| +	/*set Power-On-Control Select of GPIO PE group*/
 | |
| +	if(of_property_read_bool(np, "ingenic,poc-v1.8")) {
 | |
| +		pdata->poc_v18 = 1;
 | |
| +	}
 | |
| +
 | |
| +	/* if(of_property_read_bool(np, "ingenic,removal-dontcare")) { */
 | |
| +	/* 	pdata->removal = DONTCARE; */
 | |
| +	/* } else if(of_property_read_bool(np, "ingenic,removal-nonremovable")) { */
 | |
| +	/* 	pdata->removal = NONREMOVABLE; */
 | |
| +	/* } else if(of_property_read_bool(np, "ingenic,removal-removable")) { */
 | |
| +	/* 	pdata->removal = REMOVABLE; */
 | |
| +	/* } else if(of_property_read_bool(np, "ingenic,removal-manual")) { */
 | |
| +	/* 	pdata->removal = MANUAL; */
 | |
| +	/* }; */
 | |
| +
 | |
| +	/* mmc_of_parse_voltage(np, &pdata->ocr_avail); */
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +#else
 | |
| +static int sdhci_ingenic_parse_dt(struct device *dev,
 | |
| +								  struct sdhci_host *host,
 | |
| +								  struct sdhci_ingenic_pdata *pdata)
 | |
| +{
 | |
| +	return -EINVAL;
 | |
| +}
 | |
| +#endif
 | |
| +
 | |
| +static int sdhci_ingenic_probe(struct platform_device *pdev)
 | |
| +{
 | |
| +	struct sdhci_ingenic_pdata *pdata;
 | |
| +	struct device *dev = &pdev->dev;
 | |
| +	struct sdhci_host *host;
 | |
| +	struct sdhci_ingenic *sdhci_ing;
 | |
| +	char clkname[16];
 | |
| +	int ret, irq;
 | |
| +
 | |
| +	if (!pdev->dev.platform_data && !pdev->dev.of_node) {
 | |
| +		dev_err(dev, "no device data specified\n");
 | |
| +		return -ENOENT;
 | |
| +	}
 | |
| +
 | |
| +	irq = platform_get_irq(pdev, 0);
 | |
| +	if (irq < 0) {
 | |
| +		dev_err(dev, "no irq specified\n");
 | |
| +		return irq;
 | |
| +	}
 | |
| +
 | |
| +	host = sdhci_alloc_host(dev, sizeof(struct sdhci_ingenic));
 | |
| +	if (IS_ERR(host)) {
 | |
| +		dev_err(dev, "sdhci_alloc_host() failed\n");
 | |
| +		return PTR_ERR(host);
 | |
| +	}
 | |
| +	sdhci_ing = sdhci_priv(host);
 | |
| +
 | |
| +	pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
 | |
| +	if (!pdata) {
 | |
| +		ret = -ENOMEM;
 | |
| +		return ret;
 | |
| +	}
 | |
| +
 | |
| +	if (pdev->dev.of_node) {
 | |
| +		ret = sdhci_ingenic_parse_dt(&pdev->dev, host, pdata);
 | |
| +		if (ret)
 | |
| +			return ret;
 | |
| +	} else {
 | |
| +		memcpy(pdata, pdev->dev.platform_data, sizeof(*pdata));
 | |
| +	}
 | |
| +
 | |
| +	pdev->id = of_alias_get_id(pdev->dev.of_node, "msc");
 | |
| +
 | |
| +	sprintf(clkname, "div_msc%d", pdev->id);
 | |
| +	sdhci_ing->clk_cgu = devm_clk_get(&pdev->dev, clkname);
 | |
| +	if(!sdhci_ing->clk_cgu) {
 | |
| +		dev_err(&pdev->dev, "Failed to Get MSC clk!\n");
 | |
| +		return PTR_ERR(sdhci_ing->clk_cgu);
 | |
| +	}
 | |
| +	sprintf(clkname, "gate_msc%d", pdev->id);
 | |
| +	sdhci_ing->clk_gate = devm_clk_get(&pdev->dev, clkname);
 | |
| +	if(!sdhci_ing->clk_gate) {
 | |
| +		dev_err(&pdev->dev, "Failed to Get PWC MSC clk!\n");
 | |
| +		return PTR_ERR(sdhci_ing->clk_gate);
 | |
| +	}
 | |
| +	sdhci_ing->clk_ext = clk_get(NULL, "ext");
 | |
| +	sdhci_ing->clk_mpll = clk_get(NULL, "mpll");
 | |
| +
 | |
| +	ingenic_mmc_clk_onoff(sdhci_ing, 1);
 | |
| +
 | |
| +	sdhci_ing->host = host;
 | |
| +	sdhci_ing->dev  = &pdev->dev;
 | |
| +	sdhci_ing->pdev = pdev;
 | |
| +	sdhci_ing->pdata = pdata;
 | |
| +
 | |
| +	host->ioaddr= of_iomap(pdev->dev.of_node, 0);
 | |
| +	if (IS_ERR(host->ioaddr)) {
 | |
| +		return PTR_ERR(host->ioaddr);
 | |
| +	}
 | |
| +
 | |
| +	platform_set_drvdata(pdev, host);
 | |
| +
 | |
| +#ifdef VOLTAGE_SWITCH
 | |
| +	if (pdata->poc_v18){
 | |
| +		sdhci_ingenic_set_cpm_poc(1);
 | |
| +	} else {
 | |
| +		sdhci_ingenic_set_cpm_poc(0);
 | |
| +	}
 | |
| +#endif
 | |
| +
 | |
| +	/* sdio for WIFI init*/
 | |
| +	if (pdata->sdio_clk) {
 | |
| +		ingenic_sdio_wlan_init(&pdev->dev, pdev->id);
 | |
| +		list_add(&(sdhci_ing->list), &manual_list);
 | |
| +	}
 | |
| +
 | |
| +	if (pdata->sdr_v18 > 0 && \
 | |
| +			devm_gpio_request(dev, pdata->sdr_v18, "sdr-v18")) {
 | |
| +		printk("ERROR: no sdr-v18 pin available !!\n");
 | |
| +	}
 | |
| +
 | |
| +	host->hw_name = "ingenic-sdhci";
 | |
| +	host->ops = &sdhci_ingenic_ops;
 | |
| +	host->quirks = 0;
 | |
| +	host->irq = irq;
 | |
| +
 | |
| +	/* Software redefinition caps */
 | |
| +	host->quirks |= SDHCI_QUIRK_MISSING_CAPS;
 | |
| +	host->caps  = CAPABILITIES1_SW;
 | |
| +	host->caps1 = CAPABILITIES2_SW;
 | |
| +
 | |
| +	/* not check wp */
 | |
| +	host->quirks |= SDHCI_QUIRK_INVERTED_WRITE_PROTECT;
 | |
| +
 | |
| +	/* Setup quirks for the controller */
 | |
| +	host->quirks |= SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC;
 | |
| +	host->quirks |= SDHCI_QUIRK_NO_HISPD_BIT;
 | |
| +
 | |
| +	/* Data Timeout Counter Value */
 | |
| +	//host->quirks |= SDHCI_QUIRK_BROKEN_TIMEOUT_VAL;
 | |
| +	host->timeout_clk = 24000; //TMCLK = 24MHz
 | |
| +
 | |
| +	/* This host supports the Auto CMD12 */
 | |
| +	if(pdata->enable_autocmd12)
 | |
| +		host->quirks |= SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
 | |
| +
 | |
| +	/* PIO transfer mode */
 | |
| +	if(pdata->pio_mode){
 | |
| +		host->quirks |= SDHCI_QUIRK_BROKEN_DMA;
 | |
| +		host->quirks |= SDHCI_QUIRK_BROKEN_ADMA;
 | |
| +	}
 | |
| +	/* TODO:SoCs need BROKEN_ADMA_ZEROLEN_DESC */
 | |
| +/*	host->quirks |= SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC;*/
 | |
| +
 | |
| +	if (pdata->cd_type == SDHCI_INGENIC_CD_NONE ||
 | |
| +	    pdata->cd_type == SDHCI_INGENIC_CD_PERMANENT)
 | |
| +		host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION;
 | |
| +
 | |
| +	if (pdata->cd_type == SDHCI_INGENIC_CD_PERMANENT)
 | |
| +		host->mmc->caps = MMC_CAP_NONREMOVABLE;
 | |
| +
 | |
| +	if (pdata->pm_caps)
 | |
| +		host->mmc->pm_caps |= pdata->pm_caps;
 | |
| +
 | |
| +	host->quirks |= (SDHCI_QUIRK_32BIT_DMA_ADDR |
 | |
| +					 SDHCI_QUIRK_32BIT_DMA_SIZE);
 | |
| +
 | |
| +	/* It supports additional host capabilities if needed */
 | |
| +	if (pdata->host_caps)
 | |
| +		host->mmc->caps |= pdata->host_caps;
 | |
| +
 | |
| +	if (pdata->host_caps2)
 | |
| +		host->mmc->caps2 |= pdata->host_caps2;
 | |
| +
 | |
| +#ifdef CONFIG_PM_RUNTIME
 | |
| +	pm_runtime_enable(&pdev->dev);
 | |
| +	pm_runtime_set_autosuspend_delay(&pdev->dev, 50);
 | |
| +	pm_runtime_use_autosuspend(&pdev->dev);
 | |
| +	pm_suspend_ignore_children(&pdev->dev, 1);
 | |
| +#endif
 | |
| +
 | |
| +	ret = mmc_of_parse(host->mmc);
 | |
| +	if (ret) {
 | |
| +		dev_err(dev, "mmc_of_parse() failed\n");
 | |
| +		pm_runtime_forbid(&pdev->dev);
 | |
| +		pm_runtime_get_noresume(&pdev->dev);
 | |
| +		return ret;
 | |
| +	}
 | |
| +
 | |
| +	ret = sdhci_add_host(host);
 | |
| +	if (ret) {
 | |
| +		dev_err(dev, "sdhci_add_host() failed\n");
 | |
| +		pm_runtime_forbid(&pdev->dev);
 | |
| +		pm_runtime_get_noresume(&pdev->dev);
 | |
| +		return ret;
 | |
| +	}
 | |
| +
 | |
| +	if (pdata->cd_type == SDHCI_INGENIC_CD_GPIO &&
 | |
| +	    gpio_is_valid(pdata->gpio->cd.num))
 | |
| +		sdhci_ingenic_setup_card_detect_gpio(sdhci_ing);
 | |
| +
 | |
| +#ifdef CONFIG_PM_RUNTIME
 | |
| +	if (pdata->cd_type != SDHCI_INGENIC_CD_INTERNAL) {
 | |
| +		clk_disable_unprepare(sdhci_ing->clk_cgu);
 | |
| +		/* clk_disable_unprepare(sdhci_ing->clk_gate); */
 | |
| +	}
 | |
| +#endif
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +static int sdhci_ingenic_remove(struct platform_device *pdev)
 | |
| +{
 | |
| +	struct sdhci_host *host =  platform_get_drvdata(pdev);
 | |
| +	struct sdhci_ingenic *sdhci_ing = sdhci_priv(host);
 | |
| +
 | |
| +	if (sdhci_ing->ext_cd_irq)
 | |
| +		free_irq(sdhci_ing->ext_cd_irq, sdhci_ing);
 | |
| +
 | |
| +#ifdef CONFIG_PM_RUNTIME
 | |
| +	if (pdata->cd_type != SDHCI_INGENIC_CD_INTERNAL){
 | |
| +		/* clk_prepare_enable(sdhci_ing->clk_gate); */
 | |
| +		clk_prepare_enable(sdhci_ing->clk_cgu);
 | |
| +	}
 | |
| +#endif
 | |
| +	sdhci_remove_host(host, 1);
 | |
| +
 | |
| +	pm_runtime_dont_use_autosuspend(&pdev->dev);
 | |
| +	pm_runtime_disable(&pdev->dev);
 | |
| +
 | |
| +	clk_disable_unprepare(sdhci_ing->clk_cgu);
 | |
| +	/* clk_disable_unprepare(sdhci_ing->clk_gate); */
 | |
| +
 | |
| +	sdhci_free_host(host);
 | |
| +	platform_set_drvdata(pdev, NULL);
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +#ifdef CONFIG_PM_SLEEP
 | |
| +static int sdhci_ingenic_suspend(struct device *dev)
 | |
| +{
 | |
| +	struct sdhci_host *host = dev_get_drvdata(dev);
 | |
| +	struct sdhci_ingenic *sdhci_ing = sdhci_priv(host) ;
 | |
| +
 | |
| +	ingenic_mmc_clk_onoff(sdhci_ing, 0);
 | |
| +
 | |
| +	return sdhci_suspend_host(host);
 | |
| +}
 | |
| +
 | |
| +static int sdhci_ingenic_resume(struct device *dev)
 | |
| +{
 | |
| +	struct sdhci_host *host = dev_get_drvdata(dev);
 | |
| +	struct sdhci_ingenic *sdhci_ing = sdhci_priv(host) ;
 | |
| +
 | |
| +	ingenic_mmc_clk_onoff(sdhci_ing, 1);
 | |
| +
 | |
| +	return sdhci_resume_host(host);
 | |
| +}
 | |
| +#endif
 | |
| +
 | |
| +#ifdef CONFIG_PM
 | |
| +static int sdhci_ingenic_runtime_suspend(struct device *dev)
 | |
| +{
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +
 | |
| +static int sdhci_ingenic_runtime_resume(struct device *dev)
 | |
| +{
 | |
| +	return 0;
 | |
| +}
 | |
| +#endif
 | |
| +
 | |
| +#ifdef CONFIG_PM
 | |
| +static const struct dev_pm_ops sdhci_ingenic_pmops = {
 | |
| +	SET_SYSTEM_SLEEP_PM_OPS(sdhci_ingenic_suspend, sdhci_ingenic_resume)
 | |
| +	SET_RUNTIME_PM_OPS(sdhci_ingenic_runtime_suspend, sdhci_ingenic_runtime_resume,
 | |
| +			   NULL)
 | |
| +};
 | |
| +
 | |
| +#define SDHCI_INGENIC_PMOPS (&sdhci_ingenic_pmops)
 | |
| +
 | |
| +#else
 | |
| +#define SDHCI_INGENIC_PMOPS NULL
 | |
| +#endif
 | |
| +
 | |
| +
 | |
| +static struct platform_device_id sdhci_ingenic_driver_ids[] = {
 | |
| +	{
 | |
| +		.name		= "ingenic,sdhci",
 | |
| +		.driver_data	= (kernel_ulong_t)NULL,
 | |
| +	},
 | |
| +	{ }
 | |
| +};
 | |
| +MODULE_DEVICE_TABLE(platform, sdhci_ingenic_driver_ids);
 | |
| +
 | |
| +#ifdef CONFIG_OF
 | |
| +static const struct of_device_id sdhci_ingenic_dt_match[] = {
 | |
| +	{.compatible = "ingenic,sdhci",},
 | |
| +	{},
 | |
| +};
 | |
| +MODULE_DEVICE_TABLE(of, sdhci_ingenic_dt_match);
 | |
| +#endif
 | |
| +
 | |
| +static struct platform_driver sdhci_ingenic_driver = {
 | |
| +	.probe		= sdhci_ingenic_probe,
 | |
| +	.remove		= sdhci_ingenic_remove,
 | |
| +	.id_table	= sdhci_ingenic_driver_ids,
 | |
| +	.driver		= {
 | |
| +		.owner	= THIS_MODULE,
 | |
| +		.name	= "ingenic,sdhci",
 | |
| +		.pm	= SDHCI_INGENIC_PMOPS,
 | |
| +		.of_match_table = of_match_ptr(sdhci_ingenic_dt_match),
 | |
| +	},
 | |
| +};
 | |
| +
 | |
| +module_platform_driver(sdhci_ingenic_driver);
 | |
| +
 | |
| +
 | |
| +MODULE_DESCRIPTION("Ingenic SDHCI (MSC) driver");
 | |
| +MODULE_AUTHOR("Large Dipper <wangquan.shao@ingenic.cn>");
 | |
| +MODULE_LICENSE("GPL v2");
 | |
| +MODULE_VERSION("20160808");
 |