/*
* jmb38x_ms.c - JMicron jmb38x MemoryStick card reader
*
* Copyright (C) 2008 Alex Dubov <oakad@yahoo.com>
*
* 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/spinlock.h>
#include <linux/interrupt.h>
#include <linux/pci.h>
#include <linux/dma-mapping.h>
#include <linux/delay.h>
#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;
module_param(no_dma, bool, 0644);
enum {
DMA_ADDRESS = 0x00,
BLOCK = 0x04,
DMA_CONTROL = 0x08,
TPC_P0 = 0x0c,
TPC_P1 = 0x10,
TPC = 0x14,
HOST_CONTROL = 0x18,
DATA = 0x1c,
STATUS = 0x20,
INT_STATUS = 0x24,
INT_STATUS_ENABLE = 0x28,
INT_SIGNAL_ENABLE = 0x2c,
TIMER = 0x30,
TIMER_CONTROL = 0x34,
PAD_OUTPUT_ENABLE = 0x38,
PAD_PU_PD = 0x3c,
CLOCK_DELAY = 0x40,
ADMA_ADDRESS = 0x44,
CLOCK_CONTROL = 0x48,
LED_CONTROL = 0x4c,
VERSION = 0x50
};
struct jmb38x_ms_host {
struct jmb38x_ms *chip;
void __iomem *addr;
spinlock_t lock;
struct tasklet_struct notify;
int id;
char host_id[32];
int irq;
unsigned int block_pos;
unsigned long timeout_jiffies;
struct timer_list timer;
struct memstick_request *req;
unsigned char cmd_flags;
unsigned char io_pos;
unsigned char ifmode;
unsigned int io_word[2];
};
struct jmb38x_ms {
struct pci_dev *pdev;
int host_cnt;
struct memstick_host *hosts[];
};
#define BLOCK_COUNT_MASK 0xffff0000
#define BLOCK_SIZE_MASK 0x00000fff
#define DMA_CONTROL_ENABLE 0x00000001
#define TPC_DATA_SEL 0x00008000
#define TPC_DIR 0x00004000
#define TPC_WAIT_INT 0x00002000
#define TPC_GET_INT 0x00000800
#define TPC_CODE_SZ_MASK 0x00000700
#define TPC_DATA_SZ_MASK 0x00000007
#define HOST_CONTROL_TDELAY_EN 0x00040000
#define HOST_CONTROL_HW_OC_P 0x00010000
#define HOST_CONTROL_RESET_REQ 0x00008000
#define HOST_CONTROL_REI 0x00004000
#define HOST_CONTROL_LED 0x00000400
#define HOST_CONTROL_FAST_CLK 0x00000200
#define HOST_CONTROL_RESET 0x00000100
#define HOST_CONTROL_POWER_EN 0x00000080
#define HOST_CONTROL_CLOCK_EN 0x00000040
#define HOST_CONTROL_REO 0x00000008
#define HOST_CONTROL_IF_SHIFT 4
#define HOST_CONTROL_IF_SERIAL 0x0
#define HOST_CONTROL_IF_PAR4 0x1
#define HOST_CONTROL_IF_PAR8 0x3
#define STATUS_BUSY 0x00080000
#define STATUS_MS_DAT7 0x00040000
#define STATUS_MS_DAT6 0x00020000
#define STATUS_MS_DAT5 0x00010000
#define STATUS_MS_DAT4 0x00008000
#define STATUS_MS_DAT3 0x00004000
#define STATUS_MS_DAT2 0x00002000
#define STATUS_MS_DAT1 0x00001000
#define STATUS_MS_DAT0 0x00000800
#define STATUS_HAS_MEDIA 0x00000400
#define STATUS_FIFO_EMPTY 0x00000200
#define STATUS_FIFO_FULL 0x00000100
#define STATUS_MS_CED 0x00000080
#define STATUS_MS_ERR 0x00000040
#define STATUS_MS_BRQ 0x00000020
#define STATUS_MS_CNK 0x00000001
#define INT_STATUS_TPC_ERR 0x00080000
#define INT_STATUS_CRC_ERR 0x00040000
#define INT_STATUS_TIMER_TO 0x00020000
#define INT_STATUS_HSK_TO 0x00010000
#define INT_STATUS_ANY_ERR 0x00008000
#define INT_STATUS_FIFO_WRDY 0x00000080
#define INT_STATUS_FIFO_RRDY 0x00000040
#define INT_STATUS_MEDIA_OUT 0x00000010
#define INT_STATUS_MEDIA_IN 0x00000008
#define INT_STATUS_DMA_BOUNDARY 0x00000004
#define INT_STATUS_EOTRAN 0x00000002
#define INT_STATUS_EOTPC 0x00000001
#define INT_STATUS_ALL 0x000f801f
#define PAD_OUTPUT_ENABLE_MS 0x0F3F
#define PAD_PU_PD_OFF 0x7FFF0000
#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 0x00000002
#define CLOCK_CONTROL_60MHZ 0x00000010
#define CLOCK_CONTROL_62_5MHZ 0x00000004
#define CLOCK_CONTROL_OFF 0x00000000
#define PCI_CTL_CLOCK_DLY_ADDR 0x000000b0
enum {
CMD_READY = 0x01,
FIFO_READY = 0x02,
REG_DATA = 0x04,
DMA_DATA = 0x08
};
static unsigned int jmb38x_ms_read_data(struct jmb38x_ms_host *host,
unsigned char *buf, unsigned int length)
{
unsigned int off = 0;
while (host->io_pos && length) {
buf[off++] = host->io_word[0] & 0xff;
host->io_word[0] >>= 8;
length--;
host->io_pos--;
}
if (!length)
return off;
while (!(STATUS_FIFO_EMPTY & readl(host->addr + STATUS))) {
if (length < 4)
break;
*(unsigned int *)(buf + off) = __raw_readl(host->addr + DATA);
length -= 4;
off += 4;
}
if (length
&& !(STATUS_FIFO_EMPTY & readl(host->addr + STATUS))) {
host->io_word