diff options
Diffstat (limited to 'drivers/memstick/host')
| -rw-r--r-- | drivers/memstick/host/Kconfig | 40 | ||||
| -rw-r--r-- | drivers/memstick/host/Makefile | 7 | ||||
| -rw-r--r-- | drivers/memstick/host/jmb38x_ms.c | 140 | ||||
| -rw-r--r-- | drivers/memstick/host/r592.c | 896 | ||||
| -rw-r--r-- | drivers/memstick/host/r592.h | 175 | ||||
| -rw-r--r-- | drivers/memstick/host/rtsx_pci_ms.c | 656 | ||||
| -rw-r--r-- | drivers/memstick/host/rtsx_usb_ms.c | 839 | ||||
| -rw-r--r-- | drivers/memstick/host/tifm_ms.c | 7 | 
8 files changed, 2698 insertions, 62 deletions
diff --git a/drivers/memstick/host/Kconfig b/drivers/memstick/host/Kconfig index 4ce5c8dffb6..7310e32b599 100644 --- a/drivers/memstick/host/Kconfig +++ b/drivers/memstick/host/Kconfig @@ -5,8 +5,8 @@  comment "MemoryStick Host Controller Drivers"  config MEMSTICK_TIFM_MS -	tristate "TI Flash Media MemoryStick Interface support  (EXPERIMENTAL)" -	depends on EXPERIMENTAL && PCI +	tristate "TI Flash Media MemoryStick Interface support " +	depends on PCI  	select TIFM_CORE  	help  	  Say Y here if you want to be able to access MemoryStick cards with @@ -21,8 +21,8 @@ config MEMSTICK_TIFM_MS  	  module will be called tifm_ms.  config MEMSTICK_JMICRON_38X -	tristate "JMicron JMB38X MemoryStick interface support (EXPERIMENTAL)" -	depends on EXPERIMENTAL && PCI +	tristate "JMicron JMB38X MemoryStick interface support" +	depends on PCI  	help  	  Say Y here if you want to be able to access MemoryStick cards with @@ -30,3 +30,35 @@ config MEMSTICK_JMICRON_38X            To compile this driver as a module, choose M here: the  	  module will be called jmb38x_ms. + +config MEMSTICK_R592 +	tristate "Ricoh R5C592 MemoryStick interface support" +	depends on PCI + +	help +	  Say Y here if you want to be able to access MemoryStick cards with +	  the Ricoh R5C592 MemoryStick card reader (which is part of 5 in one +		multifunction reader) + +	  To compile this driver as a module, choose M here: the module will +	  be called r592. + +config MEMSTICK_REALTEK_PCI +	tristate "Realtek PCI-E Memstick Card Interface Driver" +	depends on MFD_RTSX_PCI +	help +	  Say Y here to include driver code to support Memstick card interface +	  of Realtek PCI-E card reader + +	  To compile this driver as a module, choose M here: the module will +	  be called rtsx_pci_ms. + +config MEMSTICK_REALTEK_USB +	tristate "Realtek USB Memstick Card Interface Driver" +	depends on MFD_RTSX_USB +	help +	  Say Y here to include driver code to support Memstick card interface +	  of Realtek RTS5129/39 series USB card reader + +	  To compile this driver as a module, choose M here: the module will +	  be called rts5139_ms. diff --git a/drivers/memstick/host/Makefile b/drivers/memstick/host/Makefile index 12530e4311d..491c9557441 100644 --- a/drivers/memstick/host/Makefile +++ b/drivers/memstick/host/Makefile @@ -2,9 +2,8 @@  # Makefile for MemoryStick host controller drivers  # -ifeq ($(CONFIG_MEMSTICK_DEBUG),y) -	EXTRA_CFLAGS			+= -DDEBUG -endif -  obj-$(CONFIG_MEMSTICK_TIFM_MS)		+= tifm_ms.o  obj-$(CONFIG_MEMSTICK_JMICRON_38X)	+= jmb38x_ms.o +obj-$(CONFIG_MEMSTICK_R592)		+= r592.o +obj-$(CONFIG_MEMSTICK_REALTEK_PCI)	+= rtsx_pci_ms.o +obj-$(CONFIG_MEMSTICK_REALTEK_USB)	+= rtsx_usb_ms.o diff --git a/drivers/memstick/host/jmb38x_ms.c b/drivers/memstick/host/jmb38x_ms.c index f2b894cd8b0..aeabaa5aedf 100644 --- a/drivers/memstick/host/jmb38x_ms.c +++ b/drivers/memstick/host/jmb38x_ms.c @@ -17,10 +17,11 @@  #include <linux/highmem.h>  #include <linux/memstick.h>  #include <linux/slab.h> +#include <linux/module.h>  #define DRIVER_NAME "jmb38x_ms" -static int no_dma; +static bool no_dma;  module_param(no_dma, bool, 0644);  enum { @@ -61,6 +62,7 @@ struct jmb38x_ms_host {  	struct memstick_request *req;  	unsigned char           cmd_flags;  	unsigned char           io_pos; +	unsigned char           ifmode;  	unsigned int            io_word[2];  }; @@ -136,15 +138,14 @@ struct jmb38x_ms {  #define PAD_PU_PD_ON_MS_SOCK0 0x5f8f0000  #define PAD_PU_PD_ON_MS_SOCK1 0x0f0f0000 +#define CLOCK_CONTROL_BY_MMIO 0x00000008  #define CLOCK_CONTROL_40MHZ   0x00000001 -#define CLOCK_CONTROL_50MHZ   0x0000000a -#define CLOCK_CONTROL_60MHZ   0x00000008 -#define CLOCK_CONTROL_62_5MHZ 0x0000000c +#define CLOCK_CONTROL_50MHZ   0x00000002 +#define CLOCK_CONTROL_60MHZ   0x00000010 +#define CLOCK_CONTROL_62_5MHZ 0x00000004  #define CLOCK_CONTROL_OFF     0x00000000  #define PCI_CTL_CLOCK_DLY_ADDR   0x000000b0 -#define PCI_CTL_CLOCK_DLY_MASK_A 0x00000f00 -#define PCI_CTL_CLOCK_DLY_MASK_B 0x0000f000  enum {  	CMD_READY    = 0x01, @@ -324,7 +325,7 @@ static int jmb38x_ms_transfer_data(struct jmb38x_ms_host *host)  			p_cnt = min(p_cnt, length);  			local_irq_save(flags); -			buf = kmap_atomic(pg, KM_BIO_SRC_IRQ) + p_off; +			buf = kmap_atomic(pg) + p_off;  		} else {  			buf = host->req->data + host->block_pos;  			p_cnt = host->req->data_len - host->block_pos; @@ -340,7 +341,7 @@ static int jmb38x_ms_transfer_data(struct jmb38x_ms_host *host)  				 : jmb38x_ms_read_reg_data(host, buf, p_cnt);  		if (host->req->long_data) { -			kunmap_atomic(buf - p_off, KM_BIO_SRC_IRQ); +			kunmap_atomic(buf - p_off);  			local_irq_restore(flags);  		} @@ -390,8 +391,13 @@ static int jmb38x_ms_issue_cmd(struct memstick_host *msh)  	if (host->req->data_dir == READ)  		cmd |= TPC_DIR; -	if (host->req->need_card_int) -		cmd |= TPC_WAIT_INT; + +	if (host->req->need_card_int) { +		if (host->ifmode == MEMSTICK_SERIAL) +			cmd |= TPC_GET_INT; +		else +			cmd |= TPC_WAIT_INT; +	}  	data = host->req->data; @@ -529,7 +535,10 @@ static irqreturn_t jmb38x_ms_isr(int irq, void *dev_id)  		if (irq_status & INT_STATUS_ANY_ERR) {  			if (irq_status & INT_STATUS_CRC_ERR)  				host->req->error = -EILSEQ; -			else +			else if (irq_status & INT_STATUS_TPC_ERR) { +				dev_dbg(&host->chip->pdev->dev, "TPC_ERR\n"); +				jmb38x_ms_complete_cmd(msh, 0); +			} else  				host->req->error = -ETIME;  		} else {  			if (host->cmd_flags & DMA_DATA) { @@ -644,7 +653,6 @@ static int jmb38x_ms_reset(struct jmb38x_ms_host *host)  		ndelay(20);  	}  	dev_dbg(&host->chip->pdev->dev, "reset_req timeout\n"); -	/* return -EIO; */  reset_next:  	writel(HOST_CONTROL_RESET | HOST_CONTROL_CLOCK_EN @@ -675,7 +683,7 @@ static int jmb38x_ms_set_param(struct memstick_host *msh,  {  	struct jmb38x_ms_host *host = memstick_priv(msh);  	unsigned int host_ctl = readl(host->addr + HOST_CONTROL); -	unsigned int clock_ctl = CLOCK_CONTROL_40MHZ, clock_delay = 0; +	unsigned int clock_ctl = CLOCK_CONTROL_BY_MMIO, clock_delay = 0;  	int rc = 0;  	switch (param) { @@ -687,9 +695,7 @@ static int jmb38x_ms_set_param(struct memstick_host *msh,  			host_ctl = 7;  			host_ctl |= HOST_CONTROL_POWER_EN -				    | HOST_CONTROL_CLOCK_EN -				    | HOST_CONTROL_HW_OC_P -				    | HOST_CONTROL_TDELAY_EN; +				 | HOST_CONTROL_CLOCK_EN;  			writel(host_ctl, host->addr + HOST_CONTROL);  			writel(host->id ? PAD_PU_PD_ON_MS_SOCK1 @@ -712,46 +718,88 @@ static int jmb38x_ms_set_param(struct memstick_host *msh,  			return -EINVAL;  		break;  	case MEMSTICK_INTERFACE: +		dev_dbg(&host->chip->pdev->dev, +			"Set Host Interface Mode to %d\n", value); +		host_ctl &= ~(HOST_CONTROL_FAST_CLK | HOST_CONTROL_REI | +			      HOST_CONTROL_REO); +		host_ctl |= HOST_CONTROL_TDELAY_EN | HOST_CONTROL_HW_OC_P;  		host_ctl &= ~(3 << HOST_CONTROL_IF_SHIFT); -		pci_read_config_dword(host->chip->pdev, -				      PCI_CTL_CLOCK_DLY_ADDR, -				      &clock_delay); -		clock_delay &= host->id ? ~PCI_CTL_CLOCK_DLY_MASK_B -					: ~PCI_CTL_CLOCK_DLY_MASK_A;  		if (value == MEMSTICK_SERIAL) { -			host_ctl &= ~HOST_CONTROL_FAST_CLK; -			host_ctl &= ~HOST_CONTROL_REO;  			host_ctl |= HOST_CONTROL_IF_SERIAL  				    << HOST_CONTROL_IF_SHIFT;  			host_ctl |= HOST_CONTROL_REI; -			clock_ctl = CLOCK_CONTROL_40MHZ; +			clock_ctl |= CLOCK_CONTROL_40MHZ; +			clock_delay = 0;  		} else if (value == MEMSTICK_PAR4) { -			host_ctl |= HOST_CONTROL_FAST_CLK | HOST_CONTROL_REO; +			host_ctl |= HOST_CONTROL_FAST_CLK;  			host_ctl |= HOST_CONTROL_IF_PAR4  				    << HOST_CONTROL_IF_SHIFT; -			host_ctl &= ~HOST_CONTROL_REI; -			clock_ctl = CLOCK_CONTROL_40MHZ; -			clock_delay |= host->id ? (4 << 12) : (4 << 8); +			host_ctl |= HOST_CONTROL_REO; +			clock_ctl |= CLOCK_CONTROL_40MHZ; +			clock_delay = 4;  		} else if (value == MEMSTICK_PAR8) {  			host_ctl |= HOST_CONTROL_FAST_CLK;  			host_ctl |= HOST_CONTROL_IF_PAR8  				    << HOST_CONTROL_IF_SHIFT; -			host_ctl &= ~(HOST_CONTROL_REI | HOST_CONTROL_REO); -			clock_ctl = CLOCK_CONTROL_50MHZ; +			clock_ctl |= CLOCK_CONTROL_50MHZ; +			clock_delay = 0;  		} else  			return -EINVAL;  		writel(host_ctl, host->addr + HOST_CONTROL); +		writel(CLOCK_CONTROL_OFF, host->addr + CLOCK_CONTROL);  		writel(clock_ctl, host->addr + CLOCK_CONTROL); -		pci_write_config_dword(host->chip->pdev, -				       PCI_CTL_CLOCK_DLY_ADDR, -				       clock_delay); +		pci_write_config_byte(host->chip->pdev, +				      PCI_CTL_CLOCK_DLY_ADDR + 1, +				      clock_delay); +		host->ifmode = value;  		break;  	};  	return 0;  } +#define PCI_PMOS0_CONTROL		0xae +#define  PMOS0_ENABLE			0x01 +#define  PMOS0_OVERCURRENT_LEVEL_2_4V	0x06 +#define  PMOS0_EN_OVERCURRENT_DEBOUNCE	0x40 +#define  PMOS0_SW_LED_POLARITY_ENABLE	0x80 +#define  PMOS0_ACTIVE_BITS (PMOS0_ENABLE | PMOS0_EN_OVERCURRENT_DEBOUNCE | \ +			    PMOS0_OVERCURRENT_LEVEL_2_4V) +#define PCI_PMOS1_CONTROL		0xbd +#define  PMOS1_ACTIVE_BITS		0x4a +#define PCI_CLOCK_CTL			0xb9 + +static int jmb38x_ms_pmos(struct pci_dev *pdev, int flag) +{ +	unsigned char val; + +	pci_read_config_byte(pdev, PCI_PMOS0_CONTROL, &val); +	if (flag) +		val |= PMOS0_ACTIVE_BITS; +	else +		val &= ~PMOS0_ACTIVE_BITS; +	pci_write_config_byte(pdev, PCI_PMOS0_CONTROL, val); +	dev_dbg(&pdev->dev, "JMB38x: set PMOS0 val 0x%x\n", val); + +	if (pci_resource_flags(pdev, 1)) { +		pci_read_config_byte(pdev, PCI_PMOS1_CONTROL, &val); +		if (flag) +			val |= PMOS1_ACTIVE_BITS; +		else +			val &= ~PMOS1_ACTIVE_BITS; +		pci_write_config_byte(pdev, PCI_PMOS1_CONTROL, val); +		dev_dbg(&pdev->dev, "JMB38x: set PMOS1 val 0x%x\n", val); +	} + +	pci_read_config_byte(pdev, PCI_CLOCK_CTL, &val); +	pci_write_config_byte(pdev, PCI_CLOCK_CTL, val & ~0x0f); +	pci_write_config_byte(pdev, PCI_CLOCK_CTL, val | 0x01); +	dev_dbg(&pdev->dev, "Clock Control by PCI config is disabled!\n"); + +        return 0; +} +  #ifdef CONFIG_PM  static int jmb38x_ms_suspend(struct pci_dev *dev, pm_message_t state) @@ -784,8 +832,7 @@ static int jmb38x_ms_resume(struct pci_dev *dev)  		return rc;  	pci_set_master(dev); -	pci_read_config_dword(dev, 0xac, &rc); -	pci_write_config_dword(dev, 0xac, rc | 0x00470000); +	jmb38x_ms_pmos(dev, 1);  	for (rc = 0; rc < jm->host_cnt; ++rc) {  		if (!jm->hosts[rc]) @@ -894,8 +941,7 @@ static int jmb38x_ms_probe(struct pci_dev *pdev,  		goto err_out;  	} -	pci_read_config_dword(pdev, 0xac, &rc); -	pci_write_config_dword(pdev, 0xac, rc | 0x00470000); +	jmb38x_ms_pmos(pdev, 1);  	cnt = jmb38x_ms_count_slots(pdev);  	if (!cnt) { @@ -976,6 +1022,8 @@ static void jmb38x_ms_remove(struct pci_dev *dev)  		jmb38x_ms_free_host(jm->hosts[cnt]);  	} +	jmb38x_ms_pmos(dev, 0); +  	pci_set_drvdata(dev, NULL);  	pci_release_regions(dev);  	pci_disable_device(dev); @@ -983,8 +1031,9 @@ static void jmb38x_ms_remove(struct pci_dev *dev)  }  static struct pci_device_id jmb38x_ms_id_tbl [] = { -	{ PCI_VENDOR_ID_JMICRON, PCI_DEVICE_ID_JMICRON_JMB38X_MS, PCI_ANY_ID, -	  PCI_ANY_ID, 0, 0, 0 }, +	{ PCI_VDEVICE(JMICRON, PCI_DEVICE_ID_JMICRON_JMB38X_MS) }, +	{ PCI_VDEVICE(JMICRON, PCI_DEVICE_ID_JMICRON_JMB385_MS) }, +	{ PCI_VDEVICE(JMICRON, PCI_DEVICE_ID_JMICRON_JMB390_MS) },  	{ }  }; @@ -997,20 +1046,9 @@ static struct pci_driver jmb38x_ms_driver = {  	.resume = jmb38x_ms_resume  }; -static int __init jmb38x_ms_init(void) -{ -	return pci_register_driver(&jmb38x_ms_driver); -} - -static void __exit jmb38x_ms_exit(void) -{ -	pci_unregister_driver(&jmb38x_ms_driver); -} +module_pci_driver(jmb38x_ms_driver);  MODULE_AUTHOR("Alex Dubov");  MODULE_DESCRIPTION("JMicron jmb38x MemoryStick driver");  MODULE_LICENSE("GPL");  MODULE_DEVICE_TABLE(pci, jmb38x_ms_id_tbl); - -module_init(jmb38x_ms_init); -module_exit(jmb38x_ms_exit); diff --git a/drivers/memstick/host/r592.c b/drivers/memstick/host/r592.c new file mode 100644 index 00000000000..31727bf285d --- /dev/null +++ b/drivers/memstick/host/r592.c @@ -0,0 +1,896 @@ +/* + * Copyright (C) 2010 - Maxim Levitsky + * driver for Ricoh memstick readers + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/freezer.h> +#include <linux/jiffies.h> +#include <linux/interrupt.h> +#include <linux/pci.h> +#include <linux/pci_ids.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/kthread.h> +#include <linux/sched.h> +#include <linux/highmem.h> +#include <asm/byteorder.h> +#include <linux/swab.h> +#include "r592.h" + +static bool r592_enable_dma = 1; +static int debug; + +static const char *tpc_names[] = { +	"MS_TPC_READ_MG_STATUS", +	"MS_TPC_READ_LONG_DATA", +	"MS_TPC_READ_SHORT_DATA", +	"MS_TPC_READ_REG", +	"MS_TPC_READ_QUAD_DATA", +	"INVALID", +	"MS_TPC_GET_INT", +	"MS_TPC_SET_RW_REG_ADRS", +	"MS_TPC_EX_SET_CMD", +	"MS_TPC_WRITE_QUAD_DATA", +	"MS_TPC_WRITE_REG", +	"MS_TPC_WRITE_SHORT_DATA", +	"MS_TPC_WRITE_LONG_DATA", +	"MS_TPC_SET_CMD", +}; + +/** + * memstick_debug_get_tpc_name - debug helper that returns string for + * a TPC number + */ +const char *memstick_debug_get_tpc_name(int tpc) +{ +	return tpc_names[tpc-1]; +} +EXPORT_SYMBOL(memstick_debug_get_tpc_name); + + +/* Read a register*/ +static inline u32 r592_read_reg(struct r592_device *dev, int address) +{ +	u32 value = readl(dev->mmio + address); +	dbg_reg("reg #%02d == 0x%08x", address, value); +	return value; +} + +/* Write a register */ +static inline void r592_write_reg(struct r592_device *dev, +							int address, u32 value) +{ +	dbg_reg("reg #%02d <- 0x%08x", address, value); +	writel(value, dev->mmio + address); +} + +/* Reads a big endian DWORD register */ +static inline u32 r592_read_reg_raw_be(struct r592_device *dev, int address) +{ +	u32 value = __raw_readl(dev->mmio + address); +	dbg_reg("reg #%02d == 0x%08x", address, value); +	return be32_to_cpu(value); +} + +/* Writes a big endian DWORD register */ +static inline void r592_write_reg_raw_be(struct r592_device *dev, +							int address, u32 value) +{ +	dbg_reg("reg #%02d <- 0x%08x", address, value); +	__raw_writel(cpu_to_be32(value), dev->mmio + address); +} + +/* Set specific bits in a register (little endian) */ +static inline void r592_set_reg_mask(struct r592_device *dev, +							int address, u32 mask) +{ +	u32 reg = readl(dev->mmio + address); +	dbg_reg("reg #%02d |= 0x%08x (old =0x%08x)", address, mask, reg); +	writel(reg | mask , dev->mmio + address); +} + +/* Clear specific bits in a register (little endian) */ +static inline void r592_clear_reg_mask(struct r592_device *dev, +						int address, u32 mask) +{ +	u32 reg = readl(dev->mmio + address); +	dbg_reg("reg #%02d &= 0x%08x (old = 0x%08x, mask = 0x%08x)", +						address, ~mask, reg, mask); +	writel(reg & ~mask, dev->mmio + address); +} + + +/* Wait for status bits while checking for errors */ +static int r592_wait_status(struct r592_device *dev, u32 mask, u32 wanted_mask) +{ +	unsigned long timeout = jiffies + msecs_to_jiffies(1000); +	u32 reg = r592_read_reg(dev, R592_STATUS); + +	if ((reg & mask) == wanted_mask) +		return 0; + +	while (time_before(jiffies, timeout)) { + +		reg = r592_read_reg(dev, R592_STATUS); + +		if ((reg & mask) == wanted_mask) +			return 0; + +		if (reg & (R592_STATUS_SEND_ERR | R592_STATUS_RECV_ERR)) +			return -EIO; + +		cpu_relax(); +	} +	return -ETIME; +} + + +/* Enable/disable device */ +static int r592_enable_device(struct r592_device *dev, bool enable) +{ +	dbg("%sabling the device", enable ? "en" : "dis"); + +	if (enable) { + +		/* Power up the card */ +		r592_write_reg(dev, R592_POWER, R592_POWER_0 | R592_POWER_1); + +		/* Perform a reset */ +		r592_set_reg_mask(dev, R592_IO, R592_IO_RESET); + +		msleep(100); +	} else +		/* Power down the card */ +		r592_write_reg(dev, R592_POWER, 0); + +	return 0; +} + +/* Set serial/parallel mode */ +static int r592_set_mode(struct r592_device *dev, bool parallel_mode) +{ +	if (!parallel_mode) { +		dbg("switching to serial mode"); + +		/* Set serial mode */ +		r592_write_reg(dev, R592_IO_MODE, R592_IO_MODE_SERIAL); + +		r592_clear_reg_mask(dev, R592_POWER, R592_POWER_20); + +	} else { +		dbg("switching to parallel mode"); + +		/* This setting should be set _before_ switch TPC */ +		r592_set_reg_mask(dev, R592_POWER, R592_POWER_20); + +		r592_clear_reg_mask(dev, R592_IO, +			R592_IO_SERIAL1 | R592_IO_SERIAL2); + +		/* Set the parallel mode now */ +		r592_write_reg(dev, R592_IO_MODE, R592_IO_MODE_PARALLEL); +	} + +	dev->parallel_mode = parallel_mode; +	return 0; +} + +/* Perform a controller reset without powering down the card */ +static void r592_host_reset(struct r592_device *dev) +{ +	r592_set_reg_mask(dev, R592_IO, R592_IO_RESET); +	msleep(100); +	r592_set_mode(dev, dev->parallel_mode); +} + +/* Disable all hardware interrupts */ +static void r592_clear_interrupts(struct r592_device *dev) +{ +	/* Disable & ACK all interrupts */ +	r592_clear_reg_mask(dev, R592_REG_MSC, IRQ_ALL_ACK_MASK); +	r592_clear_reg_mask(dev, R592_REG_MSC, IRQ_ALL_EN_MASK); +} + +/* Tests if there is an CRC error */ +static int r592_test_io_error(struct r592_device *dev) +{ +	if (!(r592_read_reg(dev, R592_STATUS) & +		(R592_STATUS_SEND_ERR | R592_STATUS_RECV_ERR))) +		return 0; + +	return -EIO; +} + +/* Ensure that FIFO is ready for use */ +static int r592_test_fifo_empty(struct r592_device *dev) +{ +	if (r592_read_reg(dev, R592_REG_MSC) & R592_REG_MSC_FIFO_EMPTY) +		return 0; + +	dbg("FIFO not ready, trying to reset the device"); +	r592_host_reset(dev); + +	if (r592_read_reg(dev, R592_REG_MSC) & R592_REG_MSC_FIFO_EMPTY) +		return 0; + +	message("FIFO still not ready, giving up"); +	return -EIO; +} + +/* Activates the DMA transfer from to FIFO */ +static void r592_start_dma(struct r592_device *dev, bool is_write) +{ +	unsigned long flags; +	u32 reg; +	spin_lock_irqsave(&dev->irq_lock, flags); + +	/* Ack interrupts (just in case) + enable them */ +	r592_clear_reg_mask(dev, R592_REG_MSC, DMA_IRQ_ACK_MASK); +	r592_set_reg_mask(dev, R592_REG_MSC, DMA_IRQ_EN_MASK); + +	/* Set DMA address */ +	r592_write_reg(dev, R592_FIFO_DMA, sg_dma_address(&dev->req->sg)); + +	/* Enable the DMA */ +	reg = r592_read_reg(dev, R592_FIFO_DMA_SETTINGS); +	reg |= R592_FIFO_DMA_SETTINGS_EN; + +	if (!is_write) +		reg |= R592_FIFO_DMA_SETTINGS_DIR; +	else +		reg &= ~R592_FIFO_DMA_SETTINGS_DIR; +	r592_write_reg(dev, R592_FIFO_DMA_SETTINGS, reg); + +	spin_unlock_irqrestore(&dev->irq_lock, flags); +} + +/* Cleanups DMA related settings */ +static void r592_stop_dma(struct r592_device *dev, int error) +{ +	r592_clear_reg_mask(dev, R592_FIFO_DMA_SETTINGS, +		R592_FIFO_DMA_SETTINGS_EN); + +	/* This is only a precation */ +	r592_write_reg(dev, R592_FIFO_DMA, +			dev->dummy_dma_page_physical_address); + +	r592_clear_reg_mask(dev, R592_REG_MSC, DMA_IRQ_EN_MASK); +	r592_clear_reg_mask(dev, R592_REG_MSC, DMA_IRQ_ACK_MASK); +	dev->dma_error = error; +} + +/* Test if hardware supports DMA */ +static void r592_check_dma(struct r592_device *dev) +{ +	dev->dma_capable = r592_enable_dma && +		(r592_read_reg(dev, R592_FIFO_DMA_SETTINGS) & +			R592_FIFO_DMA_SETTINGS_CAP); +} + +/* Transfers fifo contents in/out using DMA */ +static int r592_transfer_fifo_dma(struct r592_device *dev) +{ +	int len, sg_count; +	bool is_write; + +	if (!dev->dma_capable || !dev->req->long_data) +		return -EINVAL; + +	len = dev->req->sg.length; +	is_write = dev->req->data_dir == WRITE; + +	if (len != R592_LFIFO_SIZE) +		return -EINVAL; + +	dbg_verbose("doing dma transfer"); + +	dev->dma_error = 0; +	reinit_completion(&dev->dma_done); + +	/* TODO: hidden assumption about nenth beeing always 1 */ +	sg_count = dma_map_sg(&dev->pci_dev->dev, &dev->req->sg, 1, is_write ? +		PCI_DMA_TODEVICE : PCI_DMA_FROMDEVICE); + +	if (sg_count != 1 || +			(sg_dma_len(&dev->req->sg) < dev->req->sg.length)) { +		message("problem in dma_map_sg"); +		return -EIO; +	} + +	r592_start_dma(dev, is_write); + +	/* Wait for DMA completion */ +	if (!wait_for_completion_timeout( +			&dev->dma_done, msecs_to_jiffies(1000))) { +		message("DMA timeout"); +		r592_stop_dma(dev, -ETIMEDOUT); +	} + +	dma_unmap_sg(&dev->pci_dev->dev, &dev->req->sg, 1, is_write ? +		PCI_DMA_TODEVICE : PCI_DMA_FROMDEVICE); + + +	return dev->dma_error; +} + +/* + * Writes the FIFO in 4 byte chunks. + * If length isn't 4 byte aligned, rest of the data if put to a fifo + * to be written later + * Use r592_flush_fifo_write to flush that fifo when writing for the + * last time + */ +static void r592_write_fifo_pio(struct r592_device *dev, +					unsigned char *buffer, int len) +{ +	/* flush spill from former write */ +	if (!kfifo_is_empty(&dev->pio_fifo)) { + +		u8 tmp[4] = {0}; +		int copy_len = kfifo_in(&dev->pio_fifo, buffer, len); + +		if (!kfifo_is_full(&dev->pio_fifo)) +			return; +		len -= copy_len; +		buffer += copy_len; + +		copy_len = kfifo_out(&dev->pio_fifo, tmp, 4); +		WARN_ON(copy_len != 4); +		r592_write_reg_raw_be(dev, R592_FIFO_PIO, *(u32 *)tmp); +	} + +	WARN_ON(!kfifo_is_empty(&dev->pio_fifo)); + +	/* write full dwords */ +	while (len >= 4) { +		r592_write_reg_raw_be(dev, R592_FIFO_PIO, *(u32 *)buffer); +		buffer += 4; +		len -= 4; +	} + +	/* put remaining bytes to the spill */ +	if (len) +		kfifo_in(&dev->pio_fifo, buffer, len); +} + +/* Flushes the temporary FIFO used to make aligned DWORD writes */ +static void r592_flush_fifo_write(struct r592_device *dev) +{ +	u8 buffer[4] = { 0 }; +	int len; + +	if (kfifo_is_empty(&dev->pio_fifo)) +		return; + +	len = kfifo_out(&dev->pio_fifo, buffer, 4); +	r592_write_reg_raw_be(dev, R592_FIFO_PIO, *(u32 *)buffer); +} + +/* + * Read a fifo in 4 bytes chunks. + * If input doesn't fit the buffer, it places bytes of last dword in spill + * buffer, so that they don't get lost on last read, just throw these away. + */ +static void r592_read_fifo_pio(struct r592_device *dev, +						unsigned char *buffer, int len) +{ +	u8 tmp[4]; + +	/* Read from last spill */ +	if (!kfifo_is_empty(&dev->pio_fifo)) { +		int bytes_copied = +			kfifo_out(&dev->pio_fifo, buffer, min(4, len)); +		buffer += bytes_copied; +		len -= bytes_copied; + +		if (!kfifo_is_empty(&dev->pio_fifo)) +			return; +	} + +	/* Reads dwords from FIFO */ +	while (len >= 4) { +		*(u32 *)buffer = r592_read_reg_raw_be(dev, R592_FIFO_PIO); +		buffer += 4; +		len -= 4; +	} + +	if (len) { +		*(u32 *)tmp = r592_read_reg_raw_be(dev, R592_FIFO_PIO); +		kfifo_in(&dev->pio_fifo, tmp, 4); +		len -= kfifo_out(&dev->pio_fifo, buffer, len); +	} + +	WARN_ON(len); +	return; +} + +/* Transfers actual data using PIO. */ +static int r592_transfer_fifo_pio(struct r592_device *dev) +{ +	unsigned long flags; + +	bool is_write = dev->req->tpc >= MS_TPC_SET_RW_REG_ADRS; +	struct sg_mapping_iter miter; + +	kfifo_reset(&dev->pio_fifo); + +	if (!dev->req->long_data) { +		if (is_write) { +			r592_write_fifo_pio(dev, dev->req->data, +							dev->req->data_len); +			r592_flush_fifo_write(dev); +		} else +			r592_read_fifo_pio(dev, dev->req->data, +							dev->req->data_len); +		return 0; +	} + +	local_irq_save(flags); +	sg_miter_start(&miter, &dev->req->sg, 1, SG_MITER_ATOMIC | +		(is_write ? SG_MITER_FROM_SG : SG_MITER_TO_SG)); + +	/* Do the transfer fifo<->memory*/ +	while (sg_miter_next(&miter)) +		if (is_write) +			r592_write_fifo_pio(dev, miter.addr, miter.length); +		else +			r592_read_fifo_pio(dev, miter.addr, miter.length); + + +	/* Write last few non aligned bytes*/ +	if (is_write) +		r592_flush_fifo_write(dev); + +	sg_miter_stop(&miter); +	local_irq_restore(flags); +	return 0; +} + +/* Executes one TPC (data is read/written from small or large fifo) */ +static void r592_execute_tpc(struct r592_device *dev) +{ +	bool is_write; +	int len, error; +	u32 status, reg; + +	if (!dev->req) { +		message("BUG: tpc execution without request!"); +		return; +	} + +	is_write = dev->req->tpc >= MS_TPC_SET_RW_REG_ADRS; +	len = dev->req->long_data ? +		dev->req->sg.length : dev->req->data_len; + +	/* Ensure that FIFO can hold the input data */ +	if (len > R592_LFIFO_SIZE) { +		message("IO: hardware doesn't support TPCs longer that 512"); +		error = -ENOSYS; +		goto out; +	} + +	if (!(r592_read_reg(dev, R592_REG_MSC) & R592_REG_MSC_PRSNT)) { +		dbg("IO: refusing to send TPC because card is absent"); +		error = -ENODEV; +		goto out; +	} + +	dbg("IO: executing %s LEN=%d", +			memstick_debug_get_tpc_name(dev->req->tpc), len); + +	/* Set IO direction */ +	if (is_write) +		r592_set_reg_mask(dev, R592_IO, R592_IO_DIRECTION); +	else +		r592_clear_reg_mask(dev, R592_IO, R592_IO_DIRECTION); + + +	error = r592_test_fifo_empty(dev); +	if (error) +		goto out; + +	/* Transfer write data */ +	if (is_write) { +		error = r592_transfer_fifo_dma(dev); +		if (error == -EINVAL) +			error = r592_transfer_fifo_pio(dev); +	} + +	if (error) +		goto out; + +	/* Trigger the TPC */ +	reg = (len << R592_TPC_EXEC_LEN_SHIFT) | +		(dev->req->tpc << R592_TPC_EXEC_TPC_SHIFT) | +			R592_TPC_EXEC_BIG_FIFO; + +	r592_write_reg(dev, R592_TPC_EXEC, reg); + +	/* Wait for TPC completion */ +	status = R592_STATUS_RDY; +	if (dev->req->need_card_int) +		status |= R592_STATUS_CED; + +	error = r592_wait_status(dev, status, status); +	if (error) { +		message("card didn't respond"); +		goto out; +	} + +	/* Test IO errors */ +	error = r592_test_io_error(dev); +	if (error) { +		dbg("IO error"); +		goto out; +	} + +	/* Read data from FIFO */ +	if (!is_write) { +		error = r592_transfer_fifo_dma(dev); +		if (error == -EINVAL) +			error = r592_transfer_fifo_pio(dev); +	} + +	/* read INT reg. This can be shortened with shifts, but that way +		its more readable */ +	if (dev->parallel_mode && dev->req->need_card_int) { + +		dev->req->int_reg = 0; +		status = r592_read_reg(dev, R592_STATUS); + +		if (status & R592_STATUS_P_CMDNACK) +			dev->req->int_reg |= MEMSTICK_INT_CMDNAK; +		if (status & R592_STATUS_P_BREQ) +			dev->req->int_reg |= MEMSTICK_INT_BREQ; +		if (status & R592_STATUS_P_INTERR) +			dev->req->int_reg |= MEMSTICK_INT_ERR; +		if (status & R592_STATUS_P_CED) +			dev->req->int_reg |= MEMSTICK_INT_CED; +	} + +	if (error) +		dbg("FIFO read error"); +out: +	dev->req->error = error; +	r592_clear_reg_mask(dev, R592_REG_MSC, R592_REG_MSC_LED); +	return; +} + +/* Main request processing thread */ +static int r592_process_thread(void *data) +{ +	int error; +	struct r592_device *dev = (struct r592_device *)data; +	unsigned long flags; + +	while (!kthread_should_stop()) { +		spin_lock_irqsave(&dev->io_thread_lock, flags); +		set_current_state(TASK_INTERRUPTIBLE); +		error = memstick_next_req(dev->host, &dev->req); +		spin_unlock_irqrestore(&dev->io_thread_lock, flags); + +		if (error) { +			if (error == -ENXIO || error == -EAGAIN) { +				dbg_verbose("IO: done IO, sleeping"); +			} else { +				dbg("IO: unknown error from " +					"memstick_next_req %d", error); +			} + +			if (kthread_should_stop()) +				set_current_state(TASK_RUNNING); + +			schedule(); +		} else { +			set_current_state(TASK_RUNNING); +			r592_execute_tpc(dev); +		} +	} +	return 0; +} + +/* Reprogram chip to detect change in card state */ +/* eg, if card is detected, arm it to detect removal, and vice versa */ +static void r592_update_card_detect(struct r592_device *dev) +{ +	u32 reg = r592_read_reg(dev, R592_REG_MSC); +	bool card_detected = reg & R592_REG_MSC_PRSNT; + +	dbg("update card detect. card state: %s", card_detected ? +		"present" : "absent"); + +	reg &= ~((R592_REG_MSC_IRQ_REMOVE | R592_REG_MSC_IRQ_INSERT) << 16); + +	if (card_detected) +		reg |= (R592_REG_MSC_IRQ_REMOVE << 16); +	else +		reg |= (R592_REG_MSC_IRQ_INSERT << 16); + +	r592_write_reg(dev, R592_REG_MSC, reg); +} + +/* Timer routine that fires 1 second after last card detection event, */ +static void r592_detect_timer(long unsigned int data) +{ +	struct r592_device *dev = (struct r592_device *)data; +	r592_update_card_detect(dev); +	memstick_detect_change(dev->host); +} + +/* Interrupt handler */ +static irqreturn_t r592_irq(int irq, void *data) +{ +	struct r592_device *dev = (struct r592_device *)data; +	irqreturn_t ret = IRQ_NONE; +	u32 reg; +	u16 irq_enable, irq_status; +	unsigned long flags; +	int error; + +	spin_lock_irqsave(&dev->irq_lock, flags); + +	reg = r592_read_reg(dev, R592_REG_MSC); +	irq_enable = reg >> 16; +	irq_status = reg & 0xFFFF; + +	/* Ack the interrupts */ +	reg &= ~irq_status; +	r592_write_reg(dev, R592_REG_MSC, reg); + +	/* Get the IRQ status minus bits that aren't enabled */ +	irq_status &= (irq_enable); + +	/* Due to limitation of memstick core, we don't look at bits that +		indicate that card was removed/inserted and/or present */ +	if (irq_status & (R592_REG_MSC_IRQ_INSERT | R592_REG_MSC_IRQ_REMOVE)) { + +		bool card_was_added = irq_status & R592_REG_MSC_IRQ_INSERT; +		ret = IRQ_HANDLED; + +		message("IRQ: card %s", card_was_added ? "added" : "removed"); + +		mod_timer(&dev->detect_timer, +			jiffies + msecs_to_jiffies(card_was_added ? 500 : 50)); +	} + +	if (irq_status & +		(R592_REG_MSC_FIFO_DMA_DONE | R592_REG_MSC_FIFO_DMA_ERR)) { +		ret = IRQ_HANDLED; + +		if (irq_status & R592_REG_MSC_FIFO_DMA_ERR) { +			message("IRQ: DMA error"); +			error = -EIO; +		} else { +			dbg_verbose("IRQ: dma done"); +			error = 0; +		} + +		r592_stop_dma(dev, error); +		complete(&dev->dma_done); +	} + +	spin_unlock_irqrestore(&dev->irq_lock, flags); +	return ret; +} + +/* External inteface: set settings */ +static int r592_set_param(struct memstick_host *host, +			enum memstick_param param, int value) +{ +	struct r592_device *dev = memstick_priv(host); + +	switch (param) { +	case MEMSTICK_POWER: +		switch (value) { +		case MEMSTICK_POWER_ON: +			return r592_enable_device(dev, true); +		case MEMSTICK_POWER_OFF: +			return r592_enable_device(dev, false); +		default: +			return -EINVAL; +		} +	case MEMSTICK_INTERFACE: +		switch (value) { +		case MEMSTICK_SERIAL: +			return r592_set_mode(dev, 0); +		case MEMSTICK_PAR4: +			return r592_set_mode(dev, 1); +		default: +			return -EINVAL; +		} +	default: +		return -EINVAL; +	} +} + +/* External interface: submit requests */ +static void r592_submit_req(struct memstick_host *host) +{ +	struct r592_device *dev = memstick_priv(host); +	unsigned long flags; + +	if (dev->req) +		return; + +	spin_lock_irqsave(&dev->io_thread_lock, flags); +	if (wake_up_process(dev->io_thread)) +		dbg_verbose("IO thread woken to process requests"); +	spin_unlock_irqrestore(&dev->io_thread_lock, flags); +} + +static const struct pci_device_id r592_pci_id_tbl[] = { + +	{ PCI_VDEVICE(RICOH, 0x0592), }, +	{ }, +}; + +/* Main entry */ +static int r592_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ +	int error = -ENOMEM; +	struct memstick_host *host; +	struct r592_device *dev; + +	/* Allocate memory */ +	host = memstick_alloc_host(sizeof(struct r592_device), &pdev->dev); +	if (!host) +		goto error1; + +	dev = memstick_priv(host); +	dev->host = host; +	dev->pci_dev = pdev; +	pci_set_drvdata(pdev, dev); + +	/* pci initialization */ +	error = pci_enable_device(pdev); +	if (error) +		goto error2; + +	pci_set_master(pdev); +	error = pci_set_dma_mask(pdev, DMA_BIT_MASK(32)); +	if (error) +		goto error3; + +	error = pci_request_regions(pdev, DRV_NAME); +	if (error) +		goto error3; + +	dev->mmio = pci_ioremap_bar(pdev, 0); +	if (!dev->mmio) +		goto error4; + +	dev->irq = pdev->irq; +	spin_lock_init(&dev->irq_lock); +	spin_lock_init(&dev->io_thread_lock); +	init_completion(&dev->dma_done); +	INIT_KFIFO(dev->pio_fifo); +	setup_timer(&dev->detect_timer, +		r592_detect_timer, (long unsigned int)dev); + +	/* Host initialization */ +	host->caps = MEMSTICK_CAP_PAR4; +	host->request = r592_submit_req; +	host->set_param = r592_set_param; +	r592_check_dma(dev); + +	dev->io_thread = kthread_run(r592_process_thread, dev, "r592_io"); +	if (IS_ERR(dev->io_thread)) { +		error = PTR_ERR(dev->io_thread); +		goto error5; +	} + +	/* This is just a precation, so don't fail */ +	dev->dummy_dma_page = pci_alloc_consistent(pdev, PAGE_SIZE, +		&dev->dummy_dma_page_physical_address); +	r592_stop_dma(dev , 0); + +	if (request_irq(dev->irq, &r592_irq, IRQF_SHARED, +			  DRV_NAME, dev)) +		goto error6; + +	r592_update_card_detect(dev); +	if (memstick_add_host(host)) +		goto error7; + +	message("driver successfully loaded"); +	return 0; +error7: +	free_irq(dev->irq, dev); +error6: +	if (dev->dummy_dma_page) +		pci_free_consistent(pdev, PAGE_SIZE, dev->dummy_dma_page, +			dev->dummy_dma_page_physical_address); + +	kthread_stop(dev->io_thread); +error5: +	iounmap(dev->mmio); +error4: +	pci_release_regions(pdev); +error3: +	pci_disable_device(pdev); +error2: +	memstick_free_host(host); +error1: +	return error; +} + +static void r592_remove(struct pci_dev *pdev) +{ +	int error = 0; +	struct r592_device *dev = pci_get_drvdata(pdev); + +	/* Stop the processing thread. +	That ensures that we won't take any more requests */ +	kthread_stop(dev->io_thread); + +	r592_enable_device(dev, false); + +	while (!error && dev->req) { +		dev->req->error = -ETIME; +		error = memstick_next_req(dev->host, &dev->req); +	} +	memstick_remove_host(dev->host); + +	free_irq(dev->irq, dev); +	iounmap(dev->mmio); +	pci_release_regions(pdev); +	pci_disable_device(pdev); +	memstick_free_host(dev->host); + +	if (dev->dummy_dma_page) +		pci_free_consistent(pdev, PAGE_SIZE, dev->dummy_dma_page, +			dev->dummy_dma_page_physical_address); +} + +#ifdef CONFIG_PM_SLEEP +static int r592_suspend(struct device *core_dev) +{ +	struct pci_dev *pdev = to_pci_dev(core_dev); +	struct r592_device *dev = pci_get_drvdata(pdev); + +	r592_clear_interrupts(dev); +	memstick_suspend_host(dev->host); +	del_timer_sync(&dev->detect_timer); +	return 0; +} + +static int r592_resume(struct device *core_dev) +{ +	struct pci_dev *pdev = to_pci_dev(core_dev); +	struct r592_device *dev = pci_get_drvdata(pdev); + +	r592_clear_interrupts(dev); +	r592_enable_device(dev, false); +	memstick_resume_host(dev->host); +	r592_update_card_detect(dev); +	return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(r592_pm_ops, r592_suspend, r592_resume); + +MODULE_DEVICE_TABLE(pci, r592_pci_id_tbl); + +static struct pci_driver r852_pci_driver = { +	.name		= DRV_NAME, +	.id_table	= r592_pci_id_tbl, +	.probe		= r592_probe, +	.remove		= r592_remove, +	.driver.pm	= &r592_pm_ops, +}; + +module_pci_driver(r852_pci_driver); + +module_param_named(enable_dma, r592_enable_dma, bool, S_IRUGO); +MODULE_PARM_DESC(enable_dma, "Enable usage of the DMA (default)"); +module_param(debug, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "Debug level (0-3)"); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Maxim Levitsky <maximlevitsky@gmail.com>"); +MODULE_DESCRIPTION("Ricoh R5C592 Memstick/Memstick PRO card reader driver"); diff --git a/drivers/memstick/host/r592.h b/drivers/memstick/host/r592.h new file mode 100644 index 00000000000..c5726c1e883 --- /dev/null +++ b/drivers/memstick/host/r592.h @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2010 - Maxim Levitsky + * driver for Ricoh memstick readers + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef R592_H + +#include <linux/memstick.h> +#include <linux/spinlock.h> +#include <linux/interrupt.h> +#include <linux/workqueue.h> +#include <linux/kfifo.h> +#include <linux/ctype.h> + +/* write to this reg (number,len) triggers TPC execution */ +#define R592_TPC_EXEC			0x00 +#define R592_TPC_EXEC_LEN_SHIFT		16		/* Bits 16..25 are TPC len */ +#define R592_TPC_EXEC_BIG_FIFO		(1 << 26)	/* If bit 26 is set, large fifo is used (reg 48) */ +#define R592_TPC_EXEC_TPC_SHIFT		28		/* Bits 28..31 are the TPC number */ + + +/* Window for small TPC fifo (big endian)*/ +/* reads and writes always are done in  8 byte chunks */ +/* Not used in driver, because large fifo does better job */ +#define R592_SFIFO			0x08 + + +/* Status register (ms int, small fifo, IO)*/ +#define R592_STATUS			0x10 +							/* Parallel INT bits */ +#define R592_STATUS_P_CMDNACK		(1 << 16)	/* INT reg: NACK (parallel mode) */ +#define R592_STATUS_P_BREQ		(1 << 17)	/* INT reg: card ready (parallel mode)*/ +#define R592_STATUS_P_INTERR		(1 << 18)	/* INT reg: int error (parallel mode)*/ +#define R592_STATUS_P_CED		(1 << 19)	/* INT reg: command done (parallel mode) */ + +							/* Fifo status */ +#define R592_STATUS_SFIFO_FULL		(1 << 20)	/* Small Fifo almost full (last chunk is written) */ +#define R592_STATUS_SFIFO_EMPTY		(1 << 21)	/* Small Fifo empty */ + +							/* Error detection via CRC */ +#define R592_STATUS_SEND_ERR		(1 << 24)	/* Send failed */ +#define R592_STATUS_RECV_ERR		(1 << 25)	/* Receive failed */ + +							/* Card state */ +#define R592_STATUS_RDY			(1 << 28)	/* RDY signal received */ +#define R592_STATUS_CED			(1 << 29)	/* INT: Command done (serial mode)*/ +#define R592_STATUS_SFIFO_INPUT		(1 << 30)	/* Small fifo received data*/ + +#define R592_SFIFO_SIZE			32		/* total size of small fifo is 32 bytes */ +#define R592_SFIFO_PACKET		8		/* packet size of small fifo */ + +/* IO control */ +#define R592_IO				0x18 +#define	R592_IO_16			(1 << 16)	/* Set by default, can be cleared */ +#define	R592_IO_18			(1 << 18)	/* Set by default, can be cleared */ +#define	R592_IO_SERIAL1			(1 << 20)	/* Set by default, can be cleared, (cleared on parallel) */ +#define	R592_IO_22			(1 << 22)	/* Set by default, can be cleared */ +#define R592_IO_DIRECTION		(1 << 24)	/* TPC direction (1 write 0 read) */ +#define	R592_IO_26			(1 << 26)	/* Set by default, can be cleared */ +#define	R592_IO_SERIAL2			(1 << 30)	/* Set by default, can be cleared (cleared on parallel), serial doesn't work if unset */ +#define R592_IO_RESET			(1 << 31)	/* Reset, sets defaults*/ + + +/* Turns hardware on/off */ +#define R592_POWER			0x20		/* bits 0-7 writeable */ +#define R592_POWER_0			(1 << 0)	/* set on start, cleared on stop - must be set*/ +#define R592_POWER_1			(1 << 1)	/* set on start, cleared on stop - must be set*/ +#define R592_POWER_3			(1 << 3)	/* must be clear */ +#define R592_POWER_20			(1 << 5)	/* set before switch to parallel */ + +/* IO mode*/ +#define R592_IO_MODE			0x24 +#define R592_IO_MODE_SERIAL		1 +#define R592_IO_MODE_PARALLEL		3 + + +/* IRQ,card detection,large fifo (first word irq status, second enable) */ +/* IRQs are ACKed by clearing the bits */ +#define R592_REG_MSC			0x28 +#define R592_REG_MSC_PRSNT		(1 << 1)	/* card present (only status)*/ +#define R592_REG_MSC_IRQ_INSERT		(1 << 8)	/* detect insert / card insered */ +#define R592_REG_MSC_IRQ_REMOVE		(1 << 9)	/* detect removal / card removed */ +#define R592_REG_MSC_FIFO_EMPTY		(1 << 10)	/* fifo is empty */ +#define R592_REG_MSC_FIFO_DMA_DONE	(1 << 11)	/* dma enable / dma done */ + +#define R592_REG_MSC_FIFO_USER_ORN	(1 << 12)	/* set if software reads empty fifo (if R592_REG_MSC_FIFO_EMPTY is set) */ +#define R592_REG_MSC_FIFO_MISMATH	(1 << 13)	/* set if amount of data in fifo doesn't match amount in TPC */ +#define R592_REG_MSC_FIFO_DMA_ERR	(1 << 14)	/* IO failure */ +#define R592_REG_MSC_LED		(1 << 15)	/* clear to turn led off (only status)*/ + +#define DMA_IRQ_ACK_MASK \ +	(R592_REG_MSC_FIFO_DMA_DONE | R592_REG_MSC_FIFO_DMA_ERR) + +#define DMA_IRQ_EN_MASK (DMA_IRQ_ACK_MASK << 16) + +#define IRQ_ALL_ACK_MASK 0x00007F00 +#define IRQ_ALL_EN_MASK (IRQ_ALL_ACK_MASK << 16) + +/* DMA address for large FIFO read/writes*/ +#define R592_FIFO_DMA			0x2C + +/* PIO access to large FIFO (512 bytes) (big endian)*/ +#define R592_FIFO_PIO			0x30 +#define R592_LFIFO_SIZE			512		/* large fifo size */ + + +/* large FIFO DMA settings */ +#define R592_FIFO_DMA_SETTINGS		0x34 +#define R592_FIFO_DMA_SETTINGS_EN	(1 << 0)	/* DMA enabled */ +#define R592_FIFO_DMA_SETTINGS_DIR	(1 << 1)	/* Dma direction (1 read, 0 write) */ +#define R592_FIFO_DMA_SETTINGS_CAP	(1 << 24)	/* Dma is aviable */ + +/* Maybe just an delay */ +/* Bits 17..19 are just number */ +/* bit 16 is set, then bit 20 is waited */ +/* time to wait is about 50 spins * 2 ^ (bits 17..19) */ +/* seems to be possible just to ignore */ +/* Probably debug register */ +#define R592_REG38			0x38 +#define R592_REG38_CHANGE		(1 << 16)	/* Start bit */ +#define R592_REG38_DONE			(1 << 20)	/* HW set this after the delay */ +#define R592_REG38_SHIFT		17 + +/* Debug register, written (0xABCDEF00) when error happens - not used*/ +#define R592_REG_3C			0x3C + +struct r592_device { +	struct pci_dev *pci_dev; +	struct memstick_host	*host;		/* host backpointer */ +	struct memstick_request *req;		/* current request */ + +	/* Registers, IRQ */ +	void __iomem *mmio; +	int irq; +	spinlock_t irq_lock; +	spinlock_t io_thread_lock; +	struct timer_list detect_timer; + +	struct task_struct *io_thread; +	bool parallel_mode; + +	DECLARE_KFIFO(pio_fifo, u8, sizeof(u32)); + +	/* DMA area */ +	int dma_capable; +	int dma_error; +	struct completion dma_done; +	void *dummy_dma_page; +	dma_addr_t dummy_dma_page_physical_address; + +}; + +#define DRV_NAME "r592" + + +#define message(format, ...) \ +	printk(KERN_INFO DRV_NAME ": " format "\n", ## __VA_ARGS__) + +#define __dbg(level, format, ...) \ +	do { \ +		if (debug >= level) \ +			printk(KERN_DEBUG DRV_NAME \ +				": " format "\n", ## __VA_ARGS__); \ +	} while (0) + + +#define dbg(format, ...)		__dbg(1, format, ## __VA_ARGS__) +#define dbg_verbose(format, ...)	__dbg(2, format, ## __VA_ARGS__) +#define dbg_reg(format, ...)		__dbg(3, format, ## __VA_ARGS__) + +#endif diff --git a/drivers/memstick/host/rtsx_pci_ms.c b/drivers/memstick/host/rtsx_pci_ms.c new file mode 100644 index 00000000000..c880ba68575 --- /dev/null +++ b/drivers/memstick/host/rtsx_pci_ms.c @@ -0,0 +1,656 @@ +/* Realtek PCI-Express Memstick Card Interface driver + * + * Copyright(c) 2009-2013 Realtek Semiconductor Corp. All rights reserved. + * + * 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; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + * + * Author: + *   Wei WANG <wei_wang@realsil.com.cn> + */ + +#include <linux/module.h> +#include <linux/highmem.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/memstick.h> +#include <linux/mfd/rtsx_pci.h> +#include <asm/unaligned.h> + +struct realtek_pci_ms { +	struct platform_device	*pdev; +	struct rtsx_pcr		*pcr; +	struct memstick_host	*msh; +	struct memstick_request	*req; + +	struct mutex		host_mutex; +	struct work_struct	handle_req; + +	u8			ssc_depth; +	unsigned int		clock; +	unsigned char           ifmode; +	bool			eject; +}; + +static inline struct device *ms_dev(struct realtek_pci_ms *host) +{ +	return &(host->pdev->dev); +} + +static inline void ms_clear_error(struct realtek_pci_ms *host) +{ +	rtsx_pci_write_register(host->pcr, CARD_STOP, +			MS_STOP | MS_CLR_ERR, MS_STOP | MS_CLR_ERR); +} + +#ifdef DEBUG + +static void ms_print_debug_regs(struct realtek_pci_ms *host) +{ +	struct rtsx_pcr *pcr = host->pcr; +	u16 i; +	u8 *ptr; + +	/* Print MS host internal registers */ +	rtsx_pci_init_cmd(pcr); +	for (i = 0xFD40; i <= 0xFD44; i++) +		rtsx_pci_add_cmd(pcr, READ_REG_CMD, i, 0, 0); +	for (i = 0xFD52; i <= 0xFD69; i++) +		rtsx_pci_add_cmd(pcr, READ_REG_CMD, i, 0, 0); +	rtsx_pci_send_cmd(pcr, 100); + +	ptr = rtsx_pci_get_cmd_data(pcr); +	for (i = 0xFD40; i <= 0xFD44; i++) +		dev_dbg(ms_dev(host), "0x%04X: 0x%02x\n", i, *(ptr++)); +	for (i = 0xFD52; i <= 0xFD69; i++) +		dev_dbg(ms_dev(host), "0x%04X: 0x%02x\n", i, *(ptr++)); +} + +#else + +#define ms_print_debug_regs(host) + +#endif + +static int ms_power_on(struct realtek_pci_ms *host) +{ +	struct rtsx_pcr *pcr = host->pcr; +	int err; + +	rtsx_pci_init_cmd(pcr); +	rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_SELECT, 0x07, MS_MOD_SEL); +	rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_SHARE_MODE, +			CARD_SHARE_MASK, CARD_SHARE_48_MS); +	rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_CLK_EN, +			MS_CLK_EN, MS_CLK_EN); +	err = rtsx_pci_send_cmd(pcr, 100); +	if (err < 0) +		return err; + +	err = rtsx_pci_card_pull_ctl_enable(pcr, RTSX_MS_CARD); +	if (err < 0) +		return err; + +	err = rtsx_pci_card_power_on(pcr, RTSX_MS_CARD); +	if (err < 0) +		return err; + +	/* Wait ms power stable */ +	msleep(150); + +	err = rtsx_pci_write_register(pcr, CARD_OE, +			MS_OUTPUT_EN, MS_OUTPUT_EN); +	if (err < 0) +		return err; + +	return 0; +} + +static int ms_power_off(struct realtek_pci_ms *host) +{ +	struct rtsx_pcr *pcr = host->pcr; +	int err; + +	rtsx_pci_init_cmd(pcr); + +	rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_CLK_EN, MS_CLK_EN, 0); +	rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_OE, MS_OUTPUT_EN, 0); + +	err = rtsx_pci_send_cmd(pcr, 100); +	if (err < 0) +		return err; + +	err = rtsx_pci_card_power_off(pcr, RTSX_MS_CARD); +	if (err < 0) +		return err; + +	return rtsx_pci_card_pull_ctl_disable(pcr, RTSX_MS_CARD); +} + +static int ms_transfer_data(struct realtek_pci_ms *host, unsigned char data_dir, +		u8 tpc, u8 cfg, struct scatterlist *sg) +{ +	struct rtsx_pcr *pcr = host->pcr; +	int err; +	unsigned int length = sg->length; +	u16 sec_cnt = (u16)(length / 512); +	u8 val, trans_mode, dma_dir; +	struct memstick_dev *card = host->msh->card; +	bool pro_card = card->id.type == MEMSTICK_TYPE_PRO; + +	dev_dbg(ms_dev(host), "%s: tpc = 0x%02x, data_dir = %s, length = %d\n", +			__func__, tpc, (data_dir == READ) ? "READ" : "WRITE", +			length); + +	if (data_dir == READ) { +		dma_dir = DMA_DIR_FROM_CARD; +		trans_mode = pro_card ? MS_TM_AUTO_READ : MS_TM_NORMAL_READ; +	} else { +		dma_dir = DMA_DIR_TO_CARD; +		trans_mode = pro_card ? MS_TM_AUTO_WRITE : MS_TM_NORMAL_WRITE; +	} + +	rtsx_pci_init_cmd(pcr); + +	rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, MS_TPC, 0xFF, tpc); +	if (pro_card) { +		rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, MS_SECTOR_CNT_H, +				0xFF, (u8)(sec_cnt >> 8)); +		rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, MS_SECTOR_CNT_L, +				0xFF, (u8)sec_cnt); +	} +	rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, MS_TRANS_CFG, 0xFF, cfg); + +	rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, IRQSTAT0, +			DMA_DONE_INT, DMA_DONE_INT); +	rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, DMATC3, 0xFF, (u8)(length >> 24)); +	rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, DMATC2, 0xFF, (u8)(length >> 16)); +	rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, DMATC1, 0xFF, (u8)(length >> 8)); +	rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, DMATC0, 0xFF, (u8)length); +	rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, DMACTL, +			0x03 | DMA_PACK_SIZE_MASK, dma_dir | DMA_EN | DMA_512); +	rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_DATA_SOURCE, +			0x01, RING_BUFFER); + +	rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, MS_TRANSFER, +			0xFF, MS_TRANSFER_START | trans_mode); +	rtsx_pci_add_cmd(pcr, CHECK_REG_CMD, MS_TRANSFER, +			MS_TRANSFER_END, MS_TRANSFER_END); + +	rtsx_pci_send_cmd_no_wait(pcr); + +	err = rtsx_pci_transfer_data(pcr, sg, 1, data_dir == READ, 10000); +	if (err < 0) { +		ms_clear_error(host); +		return err; +	} + +	rtsx_pci_read_register(pcr, MS_TRANS_CFG, &val); +	if (pro_card) { +		if (val & (MS_INT_CMDNK | MS_INT_ERR | +				MS_CRC16_ERR | MS_RDY_TIMEOUT)) +			return -EIO; +	} else { +		if (val & (MS_CRC16_ERR | MS_RDY_TIMEOUT)) +			return -EIO; +	} + +	return 0; +} + +static int ms_write_bytes(struct realtek_pci_ms *host, u8 tpc, +		u8 cfg, u8 cnt, u8 *data, u8 *int_reg) +{ +	struct rtsx_pcr *pcr = host->pcr; +	int err, i; + +	dev_dbg(ms_dev(host), "%s: tpc = 0x%02x\n", __func__, tpc); + +	if (!data) +		return -EINVAL; + +	rtsx_pci_init_cmd(pcr); + +	for (i = 0; i < cnt; i++) +		rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, +				PPBUF_BASE2 + i, 0xFF, data[i]); +	if (cnt % 2) +		rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, +				PPBUF_BASE2 + i, 0xFF, 0xFF); + +	rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, MS_TPC, 0xFF, tpc); +	rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, MS_BYTE_CNT, 0xFF, cnt); +	rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, MS_TRANS_CFG, 0xFF, cfg); +	rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_DATA_SOURCE, +			0x01, PINGPONG_BUFFER); + +	rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, MS_TRANSFER, +			0xFF, MS_TRANSFER_START | MS_TM_WRITE_BYTES); +	rtsx_pci_add_cmd(pcr, CHECK_REG_CMD, MS_TRANSFER, +			MS_TRANSFER_END, MS_TRANSFER_END); +	if (int_reg) +		rtsx_pci_add_cmd(pcr, READ_REG_CMD, MS_TRANS_CFG, 0, 0); + +	err = rtsx_pci_send_cmd(pcr, 5000); +	if (err < 0) { +		u8 val; + +		rtsx_pci_read_register(pcr, MS_TRANS_CFG, &val); +		dev_dbg(ms_dev(host), "MS_TRANS_CFG: 0x%02x\n", val); + +		if (int_reg) +			*int_reg = val & 0x0F; + +		ms_print_debug_regs(host); + +		ms_clear_error(host); + +		if (!(tpc & 0x08)) { +			if (val & MS_CRC16_ERR) +				return -EIO; +		} else { +			if (!(val & 0x80)) { +				if (val & (MS_INT_ERR | MS_INT_CMDNK)) +					return -EIO; +			} +		} + +		return -ETIMEDOUT; +	} + +	if (int_reg) { +		u8 *ptr = rtsx_pci_get_cmd_data(pcr) + 1; +		*int_reg = *ptr & 0x0F; +	} + +	return 0; +} + +static int ms_read_bytes(struct realtek_pci_ms *host, u8 tpc, +		u8 cfg, u8 cnt, u8 *data, u8 *int_reg) +{ +	struct rtsx_pcr *pcr = host->pcr; +	int err, i; +	u8 *ptr; + +	dev_dbg(ms_dev(host), "%s: tpc = 0x%02x\n", __func__, tpc); + +	if (!data) +		return -EINVAL; + +	rtsx_pci_init_cmd(pcr); + +	rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, MS_TPC, 0xFF, tpc); +	rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, MS_BYTE_CNT, 0xFF, cnt); +	rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, MS_TRANS_CFG, 0xFF, cfg); +	rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_DATA_SOURCE, +			0x01, PINGPONG_BUFFER); + +	rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, MS_TRANSFER, +			0xFF, MS_TRANSFER_START | MS_TM_READ_BYTES); +	rtsx_pci_add_cmd(pcr, CHECK_REG_CMD, MS_TRANSFER, +			MS_TRANSFER_END, MS_TRANSFER_END); +	for (i = 0; i < cnt - 1; i++) +		rtsx_pci_add_cmd(pcr, READ_REG_CMD, PPBUF_BASE2 + i, 0, 0); +	if (cnt % 2) +		rtsx_pci_add_cmd(pcr, READ_REG_CMD, PPBUF_BASE2 + cnt, 0, 0); +	else +		rtsx_pci_add_cmd(pcr, READ_REG_CMD, +				PPBUF_BASE2 + cnt - 1, 0, 0); +	if (int_reg) +		rtsx_pci_add_cmd(pcr, READ_REG_CMD, MS_TRANS_CFG, 0, 0); + +	err = rtsx_pci_send_cmd(pcr, 5000); +	if (err < 0) { +		u8 val; + +		rtsx_pci_read_register(pcr, MS_TRANS_CFG, &val); +		dev_dbg(ms_dev(host), "MS_TRANS_CFG: 0x%02x\n", val); + +		if (int_reg) +			*int_reg = val & 0x0F; + +		ms_print_debug_regs(host); + +		ms_clear_error(host); + +		if (!(tpc & 0x08)) { +			if (val & MS_CRC16_ERR) +				return -EIO; +		} else { +			if (!(val & 0x80)) { +				if (val & (MS_INT_ERR | MS_INT_CMDNK)) +					return -EIO; +			} +		} + +		return -ETIMEDOUT; +	} + +	ptr = rtsx_pci_get_cmd_data(pcr) + 1; +	for (i = 0; i < cnt; i++) +		data[i] = *ptr++; + +	if (int_reg) +		*int_reg = *ptr & 0x0F; + +	return 0; +} + +static int rtsx_pci_ms_issue_cmd(struct realtek_pci_ms *host) +{ +	struct memstick_request *req = host->req; +	int err = 0; +	u8 cfg = 0, int_reg; + +	dev_dbg(ms_dev(host), "%s\n", __func__); + +	if (req->need_card_int) { +		if (host->ifmode != MEMSTICK_SERIAL) +			cfg = WAIT_INT; +	} + +	if (req->long_data) { +		err = ms_transfer_data(host, req->data_dir, +				req->tpc, cfg, &(req->sg)); +	} else { +		if (req->data_dir == READ) { +			err = ms_read_bytes(host, req->tpc, cfg, +					req->data_len, req->data, &int_reg); +		} else { +			err = ms_write_bytes(host, req->tpc, cfg, +					req->data_len, req->data, &int_reg); +		} +	} +	if (err < 0) +		return err; + +	if (req->need_card_int && (host->ifmode == MEMSTICK_SERIAL)) { +		err = ms_read_bytes(host, MS_TPC_GET_INT, +				NO_WAIT_INT, 1, &int_reg, NULL); +		if (err < 0) +			return err; +	} + +	if (req->need_card_int) { +		dev_dbg(ms_dev(host), "int_reg: 0x%02x\n", int_reg); + +		if (int_reg & MS_INT_CMDNK) +			req->int_reg |= MEMSTICK_INT_CMDNAK; +		if (int_reg & MS_INT_BREQ) +			req->int_reg |= MEMSTICK_INT_BREQ; +		if (int_reg & MS_INT_ERR) +			req->int_reg |= MEMSTICK_INT_ERR; +		if (int_reg & MS_INT_CED) +			req->int_reg |= MEMSTICK_INT_CED; +	} + +	return 0; +} + +static void rtsx_pci_ms_handle_req(struct work_struct *work) +{ +	struct realtek_pci_ms *host = container_of(work, +			struct realtek_pci_ms, handle_req); +	struct rtsx_pcr *pcr = host->pcr; +	struct memstick_host *msh = host->msh; +	int rc; + +	mutex_lock(&pcr->pcr_mutex); + +	rtsx_pci_start_run(pcr); + +	rtsx_pci_switch_clock(host->pcr, host->clock, host->ssc_depth, +			false, true, false); +	rtsx_pci_write_register(pcr, CARD_SELECT, 0x07, MS_MOD_SEL); +	rtsx_pci_write_register(pcr, CARD_SHARE_MODE, +			CARD_SHARE_MASK, CARD_SHARE_48_MS); + +	if (!host->req) { +		do { +			rc = memstick_next_req(msh, &host->req); +			dev_dbg(ms_dev(host), "next req %d\n", rc); + +			if (!rc) +				host->req->error = rtsx_pci_ms_issue_cmd(host); +		} while (!rc); +	} + +	mutex_unlock(&pcr->pcr_mutex); +} + +static void rtsx_pci_ms_request(struct memstick_host *msh) +{ +	struct realtek_pci_ms *host = memstick_priv(msh); + +	dev_dbg(ms_dev(host), "--> %s\n", __func__); + +	if (rtsx_pci_card_exclusive_check(host->pcr, RTSX_MS_CARD)) +		return; + +	schedule_work(&host->handle_req); +} + +static int rtsx_pci_ms_set_param(struct memstick_host *msh, +		enum memstick_param param, int value) +{ +	struct realtek_pci_ms *host = memstick_priv(msh); +	struct rtsx_pcr *pcr = host->pcr; +	unsigned int clock = 0; +	u8 ssc_depth = 0; +	int err; + +	dev_dbg(ms_dev(host), "%s: param = %d, value = %d\n", +			__func__, param, value); + +	err = rtsx_pci_card_exclusive_check(host->pcr, RTSX_MS_CARD); +	if (err) +		return err; + +	switch (param) { +	case MEMSTICK_POWER: +		if (value == MEMSTICK_POWER_ON) +			err = ms_power_on(host); +		else if (value == MEMSTICK_POWER_OFF) +			err = ms_power_off(host); +		else +			return -EINVAL; +		break; + +	case MEMSTICK_INTERFACE: +		if (value == MEMSTICK_SERIAL) { +			clock = 19000000; +			ssc_depth = RTSX_SSC_DEPTH_500K; + +			err = rtsx_pci_write_register(pcr, MS_CFG, 0x58, +					MS_BUS_WIDTH_1 | PUSH_TIME_DEFAULT); +			if (err < 0) +				return err; +		} else if (value == MEMSTICK_PAR4) { +			clock = 39000000; +			ssc_depth = RTSX_SSC_DEPTH_1M; + +			err = rtsx_pci_write_register(pcr, MS_CFG, +					0x58, MS_BUS_WIDTH_4 | PUSH_TIME_ODD); +			if (err < 0) +				return err; +		} else { +			return -EINVAL; +		} + +		err = rtsx_pci_switch_clock(pcr, clock, +				ssc_depth, false, true, false); +		if (err < 0) +			return err; + +		host->ssc_depth = ssc_depth; +		host->clock = clock; +		host->ifmode = value; +		break; +	} + +	return 0; +} + +#ifdef CONFIG_PM + +static int rtsx_pci_ms_suspend(struct platform_device *pdev, pm_message_t state) +{ +	struct realtek_pci_ms *host = platform_get_drvdata(pdev); +	struct memstick_host *msh = host->msh; + +	dev_dbg(ms_dev(host), "--> %s\n", __func__); + +	memstick_suspend_host(msh); +	return 0; +} + +static int rtsx_pci_ms_resume(struct platform_device *pdev) +{ +	struct realtek_pci_ms *host = platform_get_drvdata(pdev); +	struct memstick_host *msh = host->msh; + +	dev_dbg(ms_dev(host), "--> %s\n", __func__); + +	memstick_resume_host(msh); +	return 0; +} + +#else /* CONFIG_PM */ + +#define rtsx_pci_ms_suspend NULL +#define rtsx_pci_ms_resume NULL + +#endif /* CONFIG_PM */ + +static void rtsx_pci_ms_card_event(struct platform_device *pdev) +{ +	struct realtek_pci_ms *host = platform_get_drvdata(pdev); + +	memstick_detect_change(host->msh); +} + +static int rtsx_pci_ms_drv_probe(struct platform_device *pdev) +{ +	struct memstick_host *msh; +	struct realtek_pci_ms *host; +	struct rtsx_pcr *pcr; +	struct pcr_handle *handle = pdev->dev.platform_data; +	int rc; + +	if (!handle) +		return -ENXIO; + +	pcr = handle->pcr; +	if (!pcr) +		return -ENXIO; + +	dev_dbg(&(pdev->dev), +			": Realtek PCI-E Memstick controller found\n"); + +	msh = memstick_alloc_host(sizeof(*host), &pdev->dev); +	if (!msh) +		return -ENOMEM; + +	host = memstick_priv(msh); +	host->pcr = pcr; +	host->msh = msh; +	host->pdev = pdev; +	platform_set_drvdata(pdev, host); +	pcr->slots[RTSX_MS_CARD].p_dev = pdev; +	pcr->slots[RTSX_MS_CARD].card_event = rtsx_pci_ms_card_event; + +	mutex_init(&host->host_mutex); + +	INIT_WORK(&host->handle_req, rtsx_pci_ms_handle_req); +	msh->request = rtsx_pci_ms_request; +	msh->set_param = rtsx_pci_ms_set_param; +	msh->caps = MEMSTICK_CAP_PAR4; + +	rc = memstick_add_host(msh); +	if (rc) { +		memstick_free_host(msh); +		return rc; +	} + +	return 0; +} + +static int rtsx_pci_ms_drv_remove(struct platform_device *pdev) +{ +	struct realtek_pci_ms *host = platform_get_drvdata(pdev); +	struct rtsx_pcr *pcr; +	struct memstick_host *msh; +	int rc; + +	if (!host) +		return 0; + +	pcr = host->pcr; +	pcr->slots[RTSX_MS_CARD].p_dev = NULL; +	pcr->slots[RTSX_MS_CARD].card_event = NULL; +	msh = host->msh; +	host->eject = true; +	cancel_work_sync(&host->handle_req); + +	mutex_lock(&host->host_mutex); +	if (host->req) { +		dev_dbg(&(pdev->dev), +			"%s: Controller removed during transfer\n", +			dev_name(&msh->dev)); + +		rtsx_pci_complete_unfinished_transfer(pcr); + +		host->req->error = -ENOMEDIUM; +		do { +			rc = memstick_next_req(msh, &host->req); +			if (!rc) +				host->req->error = -ENOMEDIUM; +		} while (!rc); +	} +	mutex_unlock(&host->host_mutex); + +	memstick_remove_host(msh); +	memstick_free_host(msh); + +	dev_dbg(&(pdev->dev), +		": Realtek PCI-E Memstick controller has been removed\n"); + +	return 0; +} + +static struct platform_device_id rtsx_pci_ms_ids[] = { +	{ +		.name = DRV_NAME_RTSX_PCI_MS, +	}, { +		/* sentinel */ +	} +}; +MODULE_DEVICE_TABLE(platform, rtsx_pci_ms_ids); + +static struct platform_driver rtsx_pci_ms_driver = { +	.probe		= rtsx_pci_ms_drv_probe, +	.remove		= rtsx_pci_ms_drv_remove, +	.id_table       = rtsx_pci_ms_ids, +	.suspend	= rtsx_pci_ms_suspend, +	.resume		= rtsx_pci_ms_resume, +	.driver		= { +		.owner	= THIS_MODULE, +		.name	= DRV_NAME_RTSX_PCI_MS, +	}, +}; +module_platform_driver(rtsx_pci_ms_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Wei WANG <wei_wang@realsil.com.cn>"); +MODULE_DESCRIPTION("Realtek PCI-E Memstick Card Host Driver"); diff --git a/drivers/memstick/host/rtsx_usb_ms.c b/drivers/memstick/host/rtsx_usb_ms.c new file mode 100644 index 00000000000..a7282b7d4de --- /dev/null +++ b/drivers/memstick/host/rtsx_usb_ms.c @@ -0,0 +1,839 @@ +/* Realtek USB Memstick Card Interface driver + * + * Copyright(c) 2009-2013 Realtek Semiconductor Corp. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + * + * Author: + *   Roger Tseng <rogerable@realtek.com> + */ + +#include <linux/module.h> +#include <linux/highmem.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/workqueue.h> +#include <linux/memstick.h> +#include <linux/kthread.h> +#include <linux/mfd/rtsx_usb.h> +#include <linux/pm_runtime.h> +#include <linux/mutex.h> +#include <linux/sched.h> +#include <linux/completion.h> +#include <asm/unaligned.h> + +struct rtsx_usb_ms { +	struct platform_device	*pdev; +	struct rtsx_ucr	*ucr; +	struct memstick_host	*msh; +	struct memstick_request	*req; + +	struct mutex		host_mutex; +	struct work_struct	handle_req; + +	struct task_struct	*detect_ms; +	struct completion	detect_ms_exit; + +	u8			ssc_depth; +	unsigned int		clock; +	int			power_mode; +	unsigned char           ifmode; +	bool			eject; +}; + +static inline struct device *ms_dev(struct rtsx_usb_ms *host) +{ +	return &(host->pdev->dev); +} + +static inline void ms_clear_error(struct rtsx_usb_ms *host) +{ +	struct rtsx_ucr *ucr = host->ucr; +	rtsx_usb_ep0_write_register(ucr, CARD_STOP, +				  MS_STOP | MS_CLR_ERR, +				  MS_STOP | MS_CLR_ERR); + +	rtsx_usb_clear_dma_err(ucr); +	rtsx_usb_clear_fsm_err(ucr); +} + +#ifdef DEBUG + +static void ms_print_debug_regs(struct rtsx_usb_ms *host) +{ +	struct rtsx_ucr *ucr = host->ucr; +	u16 i; +	u8 *ptr; + +	/* Print MS host internal registers */ +	rtsx_usb_init_cmd(ucr); + +	/* MS_CFG to MS_INT_REG */ +	for (i = 0xFD40; i <= 0xFD44; i++) +		rtsx_usb_add_cmd(ucr, READ_REG_CMD, i, 0, 0); + +	/* CARD_SHARE_MODE to CARD_GPIO */ +	for (i = 0xFD51; i <= 0xFD56; i++) +		rtsx_usb_add_cmd(ucr, READ_REG_CMD, i, 0, 0); + +	/* CARD_PULL_CTLx */ +	for (i = 0xFD60; i <= 0xFD65; i++) +		rtsx_usb_add_cmd(ucr, READ_REG_CMD, i, 0, 0); + +	/* CARD_DATA_SOURCE, CARD_SELECT, CARD_CLK_EN, CARD_PWR_CTL */ +	rtsx_usb_add_cmd(ucr, READ_REG_CMD, CARD_DATA_SOURCE, 0, 0); +	rtsx_usb_add_cmd(ucr, READ_REG_CMD, CARD_SELECT, 0, 0); +	rtsx_usb_add_cmd(ucr, READ_REG_CMD, CARD_CLK_EN, 0, 0); +	rtsx_usb_add_cmd(ucr, READ_REG_CMD, CARD_PWR_CTL, 0, 0); + +	rtsx_usb_send_cmd(ucr, MODE_CR, 100); +	rtsx_usb_get_rsp(ucr, 21, 100); + +	ptr = ucr->rsp_buf; +	for (i = 0xFD40; i <= 0xFD44; i++) +		dev_dbg(ms_dev(host), "0x%04X: 0x%02x\n", i, *(ptr++)); +	for (i = 0xFD51; i <= 0xFD56; i++) +		dev_dbg(ms_dev(host), "0x%04X: 0x%02x\n", i, *(ptr++)); +	for (i = 0xFD60; i <= 0xFD65; i++) +		dev_dbg(ms_dev(host), "0x%04X: 0x%02x\n", i, *(ptr++)); + +	dev_dbg(ms_dev(host), "0x%04X: 0x%02x\n", CARD_DATA_SOURCE, *(ptr++)); +	dev_dbg(ms_dev(host), "0x%04X: 0x%02x\n", CARD_SELECT, *(ptr++)); +	dev_dbg(ms_dev(host), "0x%04X: 0x%02x\n", CARD_CLK_EN, *(ptr++)); +	dev_dbg(ms_dev(host), "0x%04X: 0x%02x\n", CARD_PWR_CTL, *(ptr++)); +} + +#else + +static void ms_print_debug_regs(struct rtsx_usb_ms *host) +{ +} + +#endif + +static int ms_pull_ctl_disable_lqfp48(struct rtsx_ucr *ucr) +{ +	rtsx_usb_init_cmd(ucr); + +	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL1, 0xFF, 0x55); +	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL2, 0xFF, 0x55); +	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL3, 0xFF, 0x95); +	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL4, 0xFF, 0x55); +	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL5, 0xFF, 0x55); +	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL6, 0xFF, 0xA5); + +	return rtsx_usb_send_cmd(ucr, MODE_C, 100); +} + +static int ms_pull_ctl_disable_qfn24(struct rtsx_ucr *ucr) +{ +	rtsx_usb_init_cmd(ucr); + +	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL1, 0xFF, 0x65); +	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL2, 0xFF, 0x55); +	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL3, 0xFF, 0x95); +	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL4, 0xFF, 0x55); +	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL5, 0xFF, 0x56); +	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL6, 0xFF, 0x59); + +	return rtsx_usb_send_cmd(ucr, MODE_C, 100); +} + +static int ms_pull_ctl_enable_lqfp48(struct rtsx_ucr *ucr) +{ +	rtsx_usb_init_cmd(ucr); + +	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL1, 0xFF, 0x55); +	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL2, 0xFF, 0x55); +	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL3, 0xFF, 0x95); +	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL4, 0xFF, 0x55); +	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL5, 0xFF, 0x55); +	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL6, 0xFF, 0xA5); + +	return rtsx_usb_send_cmd(ucr, MODE_C, 100); +} + +static int ms_pull_ctl_enable_qfn24(struct rtsx_ucr *ucr) +{ +	rtsx_usb_init_cmd(ucr); + +	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL1, 0xFF, 0x65); +	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL2, 0xFF, 0x55); +	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL3, 0xFF, 0x95); +	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL4, 0xFF, 0x55); +	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL5, 0xFF, 0x55); +	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL6, 0xFF, 0x59); + +	return rtsx_usb_send_cmd(ucr, MODE_C, 100); +} + +static int ms_power_on(struct rtsx_usb_ms *host) +{ +	struct rtsx_ucr *ucr = host->ucr; +	int err; + +	dev_dbg(ms_dev(host), "%s\n", __func__); + +	rtsx_usb_init_cmd(ucr); +	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_SELECT, 0x07, MS_MOD_SEL); +	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_SHARE_MODE, +			CARD_SHARE_MASK, CARD_SHARE_MS); +	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_CLK_EN, +			MS_CLK_EN, MS_CLK_EN); +	err = rtsx_usb_send_cmd(ucr, MODE_C, 100); +	if (err < 0) +		return err; + +	if (CHECK_PKG(ucr, LQFP48)) +		err = ms_pull_ctl_enable_lqfp48(ucr); +	else +		err = ms_pull_ctl_enable_qfn24(ucr); +	if (err < 0) +		return err; + +	err = rtsx_usb_write_register(ucr, CARD_PWR_CTL, +			POWER_MASK, PARTIAL_POWER_ON); +	if (err) +		return err; + +	usleep_range(800, 1000); + +	rtsx_usb_init_cmd(ucr); +	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PWR_CTL, +			POWER_MASK, POWER_ON); +	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_OE, +			MS_OUTPUT_EN, MS_OUTPUT_EN); + +	return rtsx_usb_send_cmd(ucr, MODE_C, 100); +} + +static int ms_power_off(struct rtsx_usb_ms *host) +{ +	struct rtsx_ucr *ucr = host->ucr; +	int err; + +	dev_dbg(ms_dev(host), "%s\n", __func__); + +	rtsx_usb_init_cmd(ucr); + +	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_CLK_EN, MS_CLK_EN, 0); +	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_OE, MS_OUTPUT_EN, 0); + +	err = rtsx_usb_send_cmd(ucr, MODE_C, 100); +	if (err < 0) +		return err; + +	if (CHECK_PKG(ucr, LQFP48)) +		return ms_pull_ctl_disable_lqfp48(ucr); + +	return ms_pull_ctl_disable_qfn24(ucr); +} + +static int ms_transfer_data(struct rtsx_usb_ms *host, unsigned char data_dir, +		u8 tpc, u8 cfg, struct scatterlist *sg) +{ +	struct rtsx_ucr *ucr = host->ucr; +	int err; +	unsigned int length = sg->length; +	u16 sec_cnt = (u16)(length / 512); +	u8 trans_mode, dma_dir, flag; +	unsigned int pipe; +	struct memstick_dev *card = host->msh->card; + +	dev_dbg(ms_dev(host), "%s: tpc = 0x%02x, data_dir = %s, length = %d\n", +			__func__, tpc, (data_dir == READ) ? "READ" : "WRITE", +			length); + +	if (data_dir == READ) { +		flag = MODE_CDIR; +		dma_dir = DMA_DIR_FROM_CARD; +		if (card->id.type != MEMSTICK_TYPE_PRO) +			trans_mode = MS_TM_NORMAL_READ; +		else +			trans_mode = MS_TM_AUTO_READ; +		pipe = usb_rcvbulkpipe(ucr->pusb_dev, EP_BULK_IN); +	} else { +		flag = MODE_CDOR; +		dma_dir = DMA_DIR_TO_CARD; +		if (card->id.type != MEMSTICK_TYPE_PRO) +			trans_mode = MS_TM_NORMAL_WRITE; +		else +			trans_mode = MS_TM_AUTO_WRITE; +		pipe = usb_sndbulkpipe(ucr->pusb_dev, EP_BULK_OUT); +	} + +	rtsx_usb_init_cmd(ucr); + +	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, MS_TPC, 0xFF, tpc); +	if (card->id.type == MEMSTICK_TYPE_PRO) { +		rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, MS_SECTOR_CNT_H, +				0xFF, (u8)(sec_cnt >> 8)); +		rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, MS_SECTOR_CNT_L, +				0xFF, (u8)sec_cnt); +	} +	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, MS_TRANS_CFG, 0xFF, cfg); + +	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, MC_DMA_TC3, +			0xFF, (u8)(length >> 24)); +	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, MC_DMA_TC2, +			0xFF, (u8)(length >> 16)); +	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, MC_DMA_TC1, +			0xFF, (u8)(length >> 8)); +	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, MC_DMA_TC0, 0xFF, +			(u8)length); +	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, MC_DMA_CTL, +			0x03 | DMA_PACK_SIZE_MASK, dma_dir | DMA_EN | DMA_512); +	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_DATA_SOURCE, +			0x01, RING_BUFFER); + +	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, MS_TRANSFER, +			0xFF, MS_TRANSFER_START | trans_mode); +	rtsx_usb_add_cmd(ucr, CHECK_REG_CMD, MS_TRANSFER, +			MS_TRANSFER_END, MS_TRANSFER_END); + +	err = rtsx_usb_send_cmd(ucr, flag | STAGE_MS_STATUS, 100); +	if (err) +		return err; + +	err = rtsx_usb_transfer_data(ucr, pipe, sg, length, +			1, NULL, 10000); +	if (err) +		goto err_out; + +	err = rtsx_usb_get_rsp(ucr, 3, 15000); +	if (err) +		goto err_out; + +	if (ucr->rsp_buf[0] & MS_TRANSFER_ERR || +	    ucr->rsp_buf[1] & (MS_CRC16_ERR | MS_RDY_TIMEOUT)) { +		err = -EIO; +		goto err_out; +	} +	return 0; +err_out: +	ms_clear_error(host); +	return err; +} + +static int ms_write_bytes(struct rtsx_usb_ms *host, u8 tpc, +		u8 cfg, u8 cnt, u8 *data, u8 *int_reg) +{ +	struct rtsx_ucr *ucr = host->ucr; +	int err, i; + +	dev_dbg(ms_dev(host), "%s: tpc = 0x%02x\n", __func__, tpc); + +	rtsx_usb_init_cmd(ucr); + +	for (i = 0; i < cnt; i++) +		rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, +				PPBUF_BASE2 + i, 0xFF, data[i]); + +	if (cnt % 2) +		rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, +				PPBUF_BASE2 + i, 0xFF, 0xFF); + +	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, MS_TPC, 0xFF, tpc); +	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, MS_BYTE_CNT, 0xFF, cnt); +	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, MS_TRANS_CFG, 0xFF, cfg); +	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_DATA_SOURCE, +			0x01, PINGPONG_BUFFER); + +	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, MS_TRANSFER, +			0xFF, MS_TRANSFER_START | MS_TM_WRITE_BYTES); +	rtsx_usb_add_cmd(ucr, CHECK_REG_CMD, MS_TRANSFER, +			MS_TRANSFER_END, MS_TRANSFER_END); +	rtsx_usb_add_cmd(ucr, READ_REG_CMD, MS_TRANS_CFG, 0, 0); + +	err = rtsx_usb_send_cmd(ucr, MODE_CR, 100); +	if (err) +		return err; + +	err = rtsx_usb_get_rsp(ucr, 2, 5000); +	if (err || (ucr->rsp_buf[0] & MS_TRANSFER_ERR)) { +		u8 val; + +		rtsx_usb_ep0_read_register(ucr, MS_TRANS_CFG, &val); +		dev_dbg(ms_dev(host), "MS_TRANS_CFG: 0x%02x\n", val); + +		if (int_reg) +			*int_reg = val & 0x0F; + +		ms_print_debug_regs(host); + +		ms_clear_error(host); + +		if (!(tpc & 0x08)) { +			if (val & MS_CRC16_ERR) +				return -EIO; +		} else { +			if (!(val & 0x80)) { +				if (val & (MS_INT_ERR | MS_INT_CMDNK)) +					return -EIO; +			} +		} + +		return -ETIMEDOUT; +	} + +	if (int_reg) +		*int_reg = ucr->rsp_buf[1] & 0x0F; + +	return 0; +} + +static int ms_read_bytes(struct rtsx_usb_ms *host, u8 tpc, +		u8 cfg, u8 cnt, u8 *data, u8 *int_reg) +{ +	struct rtsx_ucr *ucr = host->ucr; +	int err, i; +	u8 *ptr; + +	dev_dbg(ms_dev(host), "%s: tpc = 0x%02x\n", __func__, tpc); + +	rtsx_usb_init_cmd(ucr); + +	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, MS_TPC, 0xFF, tpc); +	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, MS_BYTE_CNT, 0xFF, cnt); +	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, MS_TRANS_CFG, 0xFF, cfg); +	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_DATA_SOURCE, +			0x01, PINGPONG_BUFFER); + +	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, MS_TRANSFER, +			0xFF, MS_TRANSFER_START | MS_TM_READ_BYTES); +	rtsx_usb_add_cmd(ucr, CHECK_REG_CMD, MS_TRANSFER, +			MS_TRANSFER_END, MS_TRANSFER_END); +	for (i = 0; i < cnt - 1; i++) +		rtsx_usb_add_cmd(ucr, READ_REG_CMD, PPBUF_BASE2 + i, 0, 0); +	if (cnt % 2) +		rtsx_usb_add_cmd(ucr, READ_REG_CMD, PPBUF_BASE2 + cnt, 0, 0); +	else +		rtsx_usb_add_cmd(ucr, READ_REG_CMD, +				PPBUF_BASE2 + cnt - 1, 0, 0); + +	rtsx_usb_add_cmd(ucr, READ_REG_CMD, MS_TRANS_CFG, 0, 0); + +	err = rtsx_usb_send_cmd(ucr, MODE_CR, 100); +	if (err) +		return err; + +	err = rtsx_usb_get_rsp(ucr, cnt + 2, 5000); +	if (err || (ucr->rsp_buf[0] & MS_TRANSFER_ERR)) { +		u8 val; + +		rtsx_usb_ep0_read_register(ucr, MS_TRANS_CFG, &val); +		dev_dbg(ms_dev(host), "MS_TRANS_CFG: 0x%02x\n", val); + +		if (int_reg && (host->ifmode != MEMSTICK_SERIAL)) +			*int_reg = val & 0x0F; + +		ms_print_debug_regs(host); + +		ms_clear_error(host); + +		if (!(tpc & 0x08)) { +			if (val & MS_CRC16_ERR) +				return -EIO; +		} else { +			if (!(val & 0x80)) { +				if (val & (MS_INT_ERR | MS_INT_CMDNK)) +					return -EIO; +			} +		} + +		return -ETIMEDOUT; +	} + +	ptr = ucr->rsp_buf + 1; +	for (i = 0; i < cnt; i++) +		data[i] = *ptr++; + + +	if (int_reg && (host->ifmode != MEMSTICK_SERIAL)) +		*int_reg = *ptr & 0x0F; + +	return 0; +} + +static int rtsx_usb_ms_issue_cmd(struct rtsx_usb_ms *host) +{ +	struct memstick_request *req = host->req; +	int err = 0; +	u8 cfg = 0, int_reg; + +	dev_dbg(ms_dev(host), "%s\n", __func__); + +	if (req->need_card_int) { +		if (host->ifmode != MEMSTICK_SERIAL) +			cfg = WAIT_INT; +	} + +	if (req->long_data) { +		err = ms_transfer_data(host, req->data_dir, +				req->tpc, cfg, &(req->sg)); +	} else { +		if (req->data_dir == READ) +			err = ms_read_bytes(host, req->tpc, cfg, +					req->data_len, req->data, &int_reg); +		else +			err = ms_write_bytes(host, req->tpc, cfg, +					req->data_len, req->data, &int_reg); +	} +	if (err < 0) +		return err; + +	if (req->need_card_int) { +		if (host->ifmode == MEMSTICK_SERIAL) { +			err = ms_read_bytes(host, MS_TPC_GET_INT, +					NO_WAIT_INT, 1, &req->int_reg, NULL); +			if (err < 0) +				return err; +		} else { + +			if (int_reg & MS_INT_CMDNK) +				req->int_reg |= MEMSTICK_INT_CMDNAK; +			if (int_reg & MS_INT_BREQ) +				req->int_reg |= MEMSTICK_INT_BREQ; +			if (int_reg & MS_INT_ERR) +				req->int_reg |= MEMSTICK_INT_ERR; +			if (int_reg & MS_INT_CED) +				req->int_reg |= MEMSTICK_INT_CED; +		} +		dev_dbg(ms_dev(host), "int_reg: 0x%02x\n", req->int_reg); +	} + +	return 0; +} + +static void rtsx_usb_ms_handle_req(struct work_struct *work) +{ +	struct rtsx_usb_ms *host = container_of(work, +			struct rtsx_usb_ms, handle_req); +	struct rtsx_ucr *ucr = host->ucr; +	struct memstick_host *msh = host->msh; +	int rc; + +	if (!host->req) { +		do { +			rc = memstick_next_req(msh, &host->req); +			dev_dbg(ms_dev(host), "next req %d\n", rc); + +			if (!rc) { +				mutex_lock(&ucr->dev_mutex); + +				if (rtsx_usb_card_exclusive_check(ucr, +							RTSX_USB_MS_CARD)) +					host->req->error = -EIO; +				else +					host->req->error = +						rtsx_usb_ms_issue_cmd(host); + +				mutex_unlock(&ucr->dev_mutex); + +				dev_dbg(ms_dev(host), "req result %d\n", +						host->req->error); +			} +		} while (!rc); +	} + +} + +static void rtsx_usb_ms_request(struct memstick_host *msh) +{ +	struct rtsx_usb_ms *host = memstick_priv(msh); + +	dev_dbg(ms_dev(host), "--> %s\n", __func__); + +	if (!host->eject) +		schedule_work(&host->handle_req); +} + +static int rtsx_usb_ms_set_param(struct memstick_host *msh, +		enum memstick_param param, int value) +{ +	struct rtsx_usb_ms *host = memstick_priv(msh); +	struct rtsx_ucr *ucr = host->ucr; +	unsigned int clock = 0; +	u8 ssc_depth = 0; +	int err; + +	dev_dbg(ms_dev(host), "%s: param = %d, value = %d\n", +			__func__, param, value); + +	mutex_lock(&ucr->dev_mutex); + +	err = rtsx_usb_card_exclusive_check(ucr, RTSX_USB_MS_CARD); +	if (err) +		goto out; + +	switch (param) { +	case MEMSTICK_POWER: +		if (value == host->power_mode) +			break; + +		if (value == MEMSTICK_POWER_ON) { +			pm_runtime_get_sync(ms_dev(host)); +			err = ms_power_on(host); +		} else if (value == MEMSTICK_POWER_OFF) { +			err = ms_power_off(host); +			if (host->msh->card) +				pm_runtime_put_noidle(ms_dev(host)); +			else +				pm_runtime_put(ms_dev(host)); +		} else +			err = -EINVAL; +		if (!err) +			host->power_mode = value; +		break; + +	case MEMSTICK_INTERFACE: +		if (value == MEMSTICK_SERIAL) { +			clock = 19000000; +			ssc_depth = SSC_DEPTH_512K; +			err = rtsx_usb_write_register(ucr, MS_CFG, 0x5A, +				       MS_BUS_WIDTH_1 | PUSH_TIME_DEFAULT); +			if (err < 0) +				break; +		} else if (value == MEMSTICK_PAR4) { +			clock = 39000000; +			ssc_depth = SSC_DEPTH_1M; + +			err = rtsx_usb_write_register(ucr, MS_CFG, 0x5A, +					MS_BUS_WIDTH_4 | PUSH_TIME_ODD | +					MS_NO_CHECK_INT); +			if (err < 0) +				break; +		} else { +			err = -EINVAL; +			break; +		} + +		err = rtsx_usb_switch_clock(ucr, clock, +				ssc_depth, false, true, false); +		if (err < 0) { +			dev_dbg(ms_dev(host), "switch clock failed\n"); +			break; +		} + +		host->ssc_depth = ssc_depth; +		host->clock = clock; +		host->ifmode = value; +		break; +	default: +		err = -EINVAL; +		break; +	} +out: +	mutex_unlock(&ucr->dev_mutex); + +	/* power-on delay */ +	if (param == MEMSTICK_POWER && value == MEMSTICK_POWER_ON) +		usleep_range(10000, 12000); + +	dev_dbg(ms_dev(host), "%s: return = %d\n", __func__, err); +	return err; +} + +#ifdef CONFIG_PM_SLEEP +static int rtsx_usb_ms_suspend(struct device *dev) +{ +	struct rtsx_usb_ms *host = dev_get_drvdata(dev); +	struct memstick_host *msh = host->msh; + +	dev_dbg(ms_dev(host), "--> %s\n", __func__); + +	memstick_suspend_host(msh); +	return 0; +} + +static int rtsx_usb_ms_resume(struct device *dev) +{ +	struct rtsx_usb_ms *host = dev_get_drvdata(dev); +	struct memstick_host *msh = host->msh; + +	dev_dbg(ms_dev(host), "--> %s\n", __func__); + +	memstick_resume_host(msh); +	return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +/* + * Thread function of ms card slot detection. The thread starts right after + * successful host addition. It stops while the driver removal function sets + * host->eject true. + */ +static int rtsx_usb_detect_ms_card(void *__host) +{ +	struct rtsx_usb_ms *host = (struct rtsx_usb_ms *)__host; +	struct rtsx_ucr *ucr = host->ucr; +	u8 val = 0; +	int err; + +	for (;;) { +		mutex_lock(&ucr->dev_mutex); + +		/* Check pending MS card changes */ +		err = rtsx_usb_read_register(ucr, CARD_INT_PEND, &val); +		if (err) { +			mutex_unlock(&ucr->dev_mutex); +			goto poll_again; +		} + +		/* Clear the pending */ +		rtsx_usb_write_register(ucr, CARD_INT_PEND, +				XD_INT | MS_INT | SD_INT, +				XD_INT | MS_INT | SD_INT); + +		mutex_unlock(&ucr->dev_mutex); + +		if (val & MS_INT) { +			dev_dbg(ms_dev(host), "MS slot change detected\n"); +			memstick_detect_change(host->msh); +		} + +poll_again: +		if (host->eject) +			break; + +		msleep(1000); +	} + +	complete(&host->detect_ms_exit); +	return 0; +} + +static int rtsx_usb_ms_drv_probe(struct platform_device *pdev) +{ +	struct memstick_host *msh; +	struct rtsx_usb_ms *host; +	struct rtsx_ucr *ucr; +	int err; + +	ucr = usb_get_intfdata(to_usb_interface(pdev->dev.parent)); +	if (!ucr) +		return -ENXIO; + +	dev_dbg(&(pdev->dev), +			"Realtek USB Memstick controller found\n"); + +	msh = memstick_alloc_host(sizeof(*host), &pdev->dev); +	if (!msh) +		return -ENOMEM; + +	host = memstick_priv(msh); +	host->ucr = ucr; +	host->msh = msh; +	host->pdev = pdev; +	host->power_mode = MEMSTICK_POWER_OFF; +	platform_set_drvdata(pdev, host); + +	mutex_init(&host->host_mutex); +	INIT_WORK(&host->handle_req, rtsx_usb_ms_handle_req); + +	init_completion(&host->detect_ms_exit); +	host->detect_ms = kthread_create(rtsx_usb_detect_ms_card, host, +			"rtsx_usb_ms_%d", pdev->id); +	if (IS_ERR(host->detect_ms)) { +		dev_dbg(&(pdev->dev), +				"Unable to create polling thread.\n"); +		err = PTR_ERR(host->detect_ms); +		goto err_out; +	} + +	msh->request = rtsx_usb_ms_request; +	msh->set_param = rtsx_usb_ms_set_param; +	msh->caps = MEMSTICK_CAP_PAR4; + +	pm_runtime_enable(&pdev->dev); +	err = memstick_add_host(msh); +	if (err) +		goto err_out; + +	wake_up_process(host->detect_ms); +	return 0; +err_out: +	memstick_free_host(msh); +	return err; +} + +static int rtsx_usb_ms_drv_remove(struct platform_device *pdev) +{ +	struct rtsx_usb_ms *host = platform_get_drvdata(pdev); +	struct memstick_host *msh; +	int err; + +	msh = host->msh; +	host->eject = true; +	cancel_work_sync(&host->handle_req); + +	mutex_lock(&host->host_mutex); +	if (host->req) { +		dev_dbg(&(pdev->dev), +			"%s: Controller removed during transfer\n", +			dev_name(&msh->dev)); +		host->req->error = -ENOMEDIUM; +		do { +			err = memstick_next_req(msh, &host->req); +			if (!err) +				host->req->error = -ENOMEDIUM; +		} while (!err); +	} +	mutex_unlock(&host->host_mutex); + +	wait_for_completion(&host->detect_ms_exit); +	memstick_remove_host(msh); +	memstick_free_host(msh); + +	/* Balance possible unbalanced usage count +	 * e.g. unconditional module removal +	 */ +	if (pm_runtime_active(ms_dev(host))) +		pm_runtime_put(ms_dev(host)); + +	pm_runtime_disable(&pdev->dev); +	platform_set_drvdata(pdev, NULL); + +	dev_dbg(&(pdev->dev), +		": Realtek USB Memstick controller has been removed\n"); + +	return 0; +} + +static SIMPLE_DEV_PM_OPS(rtsx_usb_ms_pm_ops, +		rtsx_usb_ms_suspend, rtsx_usb_ms_resume); + +static struct platform_device_id rtsx_usb_ms_ids[] = { +	{ +		.name = "rtsx_usb_ms", +	}, { +		/* sentinel */ +	} +}; +MODULE_DEVICE_TABLE(platform, rtsx_usb_ms_ids); + +static struct platform_driver rtsx_usb_ms_driver = { +	.probe		= rtsx_usb_ms_drv_probe, +	.remove		= rtsx_usb_ms_drv_remove, +	.id_table       = rtsx_usb_ms_ids, +	.driver		= { +		.owner	= THIS_MODULE, +		.name	= "rtsx_usb_ms", +		.pm	= &rtsx_usb_ms_pm_ops, +	}, +}; +module_platform_driver(rtsx_usb_ms_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Roger Tseng <rogerable@realtek.com>"); +MODULE_DESCRIPTION("Realtek USB Memstick Card Host Driver"); diff --git a/drivers/memstick/host/tifm_ms.c b/drivers/memstick/host/tifm_ms.c index 03f71a431c8..7bafa72f8f5 100644 --- a/drivers/memstick/host/tifm_ms.c +++ b/drivers/memstick/host/tifm_ms.c @@ -17,11 +17,12 @@  #include <linux/highmem.h>  #include <linux/scatterlist.h>  #include <linux/log2.h> +#include <linux/module.h>  #include <asm/io.h>  #define DRIVER_NAME "tifm_ms" -static int no_dma; +static bool no_dma;  module_param(no_dma, bool, 0644);  /* @@ -209,7 +210,7 @@ static unsigned int tifm_ms_transfer_data(struct tifm_ms *host)  			p_cnt = min(p_cnt, length);  			local_irq_save(flags); -			buf = kmap_atomic(pg, KM_BIO_SRC_IRQ) + p_off; +			buf = kmap_atomic(pg) + p_off;  		} else {  			buf = host->req->data + host->block_pos;  			p_cnt = host->req->data_len - host->block_pos; @@ -220,7 +221,7 @@ static unsigned int tifm_ms_transfer_data(struct tifm_ms *host)  			 : tifm_ms_read_data(host, buf, p_cnt);  		if (host->req->long_data) { -			kunmap_atomic(buf - p_off, KM_BIO_SRC_IRQ); +			kunmap_atomic(buf - p_off);  			local_irq_restore(flags);  		}  | 
