162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci *  TI FlashMedia driver
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci *  Copyright (C) 2007 Alex Dubov <oakad@yahoo.com>
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * Special thanks to Carlos Corbacho for providing various MemoryStick cards
862306a36Sopenharmony_ci * that made this driver possible.
962306a36Sopenharmony_ci */
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci#include <linux/tifm.h>
1262306a36Sopenharmony_ci#include <linux/memstick.h>
1362306a36Sopenharmony_ci#include <linux/highmem.h>
1462306a36Sopenharmony_ci#include <linux/scatterlist.h>
1562306a36Sopenharmony_ci#include <linux/log2.h>
1662306a36Sopenharmony_ci#include <linux/module.h>
1762306a36Sopenharmony_ci#include <asm/io.h>
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci#define DRIVER_NAME "tifm_ms"
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_cistatic bool no_dma;
2262306a36Sopenharmony_cimodule_param(no_dma, bool, 0644);
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci/*
2562306a36Sopenharmony_ci * Some control bits of TIFM appear to conform to Sony's reference design,
2662306a36Sopenharmony_ci * so I'm just assuming they all are.
2762306a36Sopenharmony_ci */
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci#define TIFM_MS_STAT_DRQ     0x04000
3062306a36Sopenharmony_ci#define TIFM_MS_STAT_MSINT   0x02000
3162306a36Sopenharmony_ci#define TIFM_MS_STAT_RDY     0x01000
3262306a36Sopenharmony_ci#define TIFM_MS_STAT_CRC     0x00200
3362306a36Sopenharmony_ci#define TIFM_MS_STAT_TOE     0x00100
3462306a36Sopenharmony_ci#define TIFM_MS_STAT_EMP     0x00020
3562306a36Sopenharmony_ci#define TIFM_MS_STAT_FUL     0x00010
3662306a36Sopenharmony_ci#define TIFM_MS_STAT_CED     0x00008
3762306a36Sopenharmony_ci#define TIFM_MS_STAT_ERR     0x00004
3862306a36Sopenharmony_ci#define TIFM_MS_STAT_BRQ     0x00002
3962306a36Sopenharmony_ci#define TIFM_MS_STAT_CNK     0x00001
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci#define TIFM_MS_SYS_DMA      0x10000
4262306a36Sopenharmony_ci#define TIFM_MS_SYS_RESET    0x08000
4362306a36Sopenharmony_ci#define TIFM_MS_SYS_SRAC     0x04000
4462306a36Sopenharmony_ci#define TIFM_MS_SYS_INTEN    0x02000
4562306a36Sopenharmony_ci#define TIFM_MS_SYS_NOCRC    0x01000
4662306a36Sopenharmony_ci#define TIFM_MS_SYS_INTCLR   0x00800
4762306a36Sopenharmony_ci#define TIFM_MS_SYS_MSIEN    0x00400
4862306a36Sopenharmony_ci#define TIFM_MS_SYS_FCLR     0x00200
4962306a36Sopenharmony_ci#define TIFM_MS_SYS_FDIR     0x00100
5062306a36Sopenharmony_ci#define TIFM_MS_SYS_DAM      0x00080
5162306a36Sopenharmony_ci#define TIFM_MS_SYS_DRM      0x00040
5262306a36Sopenharmony_ci#define TIFM_MS_SYS_DRQSL    0x00020
5362306a36Sopenharmony_ci#define TIFM_MS_SYS_REI      0x00010
5462306a36Sopenharmony_ci#define TIFM_MS_SYS_REO      0x00008
5562306a36Sopenharmony_ci#define TIFM_MS_SYS_BSY_MASK 0x00007
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci#define TIFM_MS_SYS_FIFO     (TIFM_MS_SYS_INTEN | TIFM_MS_SYS_MSIEN \
5862306a36Sopenharmony_ci			      | TIFM_MS_SYS_FCLR | TIFM_MS_SYS_BSY_MASK)
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci/* Hardware flags */
6162306a36Sopenharmony_cienum {
6262306a36Sopenharmony_ci	CMD_READY  = 0x01,
6362306a36Sopenharmony_ci	FIFO_READY = 0x02,
6462306a36Sopenharmony_ci	CARD_INT   = 0x04
6562306a36Sopenharmony_ci};
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_cistruct tifm_ms {
6862306a36Sopenharmony_ci	struct tifm_dev         *dev;
6962306a36Sopenharmony_ci	struct timer_list       timer;
7062306a36Sopenharmony_ci	struct memstick_request *req;
7162306a36Sopenharmony_ci	struct tasklet_struct   notify;
7262306a36Sopenharmony_ci	unsigned int            mode_mask;
7362306a36Sopenharmony_ci	unsigned int            block_pos;
7462306a36Sopenharmony_ci	unsigned long           timeout_jiffies;
7562306a36Sopenharmony_ci	unsigned char           eject:1,
7662306a36Sopenharmony_ci				use_dma:1;
7762306a36Sopenharmony_ci	unsigned char           cmd_flags;
7862306a36Sopenharmony_ci	unsigned char           io_pos;
7962306a36Sopenharmony_ci	unsigned int            io_word;
8062306a36Sopenharmony_ci};
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_cistatic unsigned int tifm_ms_read_data(struct tifm_ms *host,
8362306a36Sopenharmony_ci				      unsigned char *buf, unsigned int length)
8462306a36Sopenharmony_ci{
8562306a36Sopenharmony_ci	struct tifm_dev *sock = host->dev;
8662306a36Sopenharmony_ci	unsigned int off = 0;
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	while (host->io_pos && length) {
8962306a36Sopenharmony_ci		buf[off++] = host->io_word & 0xff;
9062306a36Sopenharmony_ci		host->io_word >>= 8;
9162306a36Sopenharmony_ci		length--;
9262306a36Sopenharmony_ci		host->io_pos--;
9362306a36Sopenharmony_ci	}
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci	if (!length)
9662306a36Sopenharmony_ci		return off;
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	while (!(TIFM_MS_STAT_EMP & readl(sock->addr + SOCK_MS_STATUS))) {
9962306a36Sopenharmony_ci		if (length < 4)
10062306a36Sopenharmony_ci			break;
10162306a36Sopenharmony_ci		*(unsigned int *)(buf + off) = __raw_readl(sock->addr
10262306a36Sopenharmony_ci							   + SOCK_MS_DATA);
10362306a36Sopenharmony_ci		length -= 4;
10462306a36Sopenharmony_ci		off += 4;
10562306a36Sopenharmony_ci	}
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	if (length
10862306a36Sopenharmony_ci	    && !(TIFM_MS_STAT_EMP & readl(sock->addr + SOCK_MS_STATUS))) {
10962306a36Sopenharmony_ci		host->io_word = readl(sock->addr + SOCK_MS_DATA);
11062306a36Sopenharmony_ci		for (host->io_pos = 4; host->io_pos; --host->io_pos) {
11162306a36Sopenharmony_ci			buf[off++] = host->io_word & 0xff;
11262306a36Sopenharmony_ci			host->io_word >>= 8;
11362306a36Sopenharmony_ci			length--;
11462306a36Sopenharmony_ci			if (!length)
11562306a36Sopenharmony_ci				break;
11662306a36Sopenharmony_ci		}
11762306a36Sopenharmony_ci	}
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	return off;
12062306a36Sopenharmony_ci}
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_cistatic unsigned int tifm_ms_write_data(struct tifm_ms *host,
12362306a36Sopenharmony_ci				       unsigned char *buf, unsigned int length)
12462306a36Sopenharmony_ci{
12562306a36Sopenharmony_ci	struct tifm_dev *sock = host->dev;
12662306a36Sopenharmony_ci	unsigned int off = 0;
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	if (host->io_pos) {
12962306a36Sopenharmony_ci		while (host->io_pos < 4 && length) {
13062306a36Sopenharmony_ci			host->io_word |=  buf[off++] << (host->io_pos * 8);
13162306a36Sopenharmony_ci			host->io_pos++;
13262306a36Sopenharmony_ci			length--;
13362306a36Sopenharmony_ci		}
13462306a36Sopenharmony_ci	}
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	if (host->io_pos == 4
13762306a36Sopenharmony_ci	    && !(TIFM_MS_STAT_FUL & readl(sock->addr + SOCK_MS_STATUS))) {
13862306a36Sopenharmony_ci		writel(TIFM_MS_SYS_FDIR | readl(sock->addr + SOCK_MS_SYSTEM),
13962306a36Sopenharmony_ci		       sock->addr + SOCK_MS_SYSTEM);
14062306a36Sopenharmony_ci		writel(host->io_word, sock->addr + SOCK_MS_DATA);
14162306a36Sopenharmony_ci		host->io_pos = 0;
14262306a36Sopenharmony_ci		host->io_word = 0;
14362306a36Sopenharmony_ci	} else if (host->io_pos) {
14462306a36Sopenharmony_ci		return off;
14562306a36Sopenharmony_ci	}
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci	if (!length)
14862306a36Sopenharmony_ci		return off;
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci	while (!(TIFM_MS_STAT_FUL & readl(sock->addr + SOCK_MS_STATUS))) {
15162306a36Sopenharmony_ci		if (length < 4)
15262306a36Sopenharmony_ci			break;
15362306a36Sopenharmony_ci		writel(TIFM_MS_SYS_FDIR | readl(sock->addr + SOCK_MS_SYSTEM),
15462306a36Sopenharmony_ci		       sock->addr + SOCK_MS_SYSTEM);
15562306a36Sopenharmony_ci		__raw_writel(*(unsigned int *)(buf + off),
15662306a36Sopenharmony_ci			     sock->addr + SOCK_MS_DATA);
15762306a36Sopenharmony_ci		length -= 4;
15862306a36Sopenharmony_ci		off += 4;
15962306a36Sopenharmony_ci	}
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	switch (length) {
16262306a36Sopenharmony_ci	case 3:
16362306a36Sopenharmony_ci		host->io_word |= buf[off + 2] << 16;
16462306a36Sopenharmony_ci		host->io_pos++;
16562306a36Sopenharmony_ci		fallthrough;
16662306a36Sopenharmony_ci	case 2:
16762306a36Sopenharmony_ci		host->io_word |= buf[off + 1] << 8;
16862306a36Sopenharmony_ci		host->io_pos++;
16962306a36Sopenharmony_ci		fallthrough;
17062306a36Sopenharmony_ci	case 1:
17162306a36Sopenharmony_ci		host->io_word |= buf[off];
17262306a36Sopenharmony_ci		host->io_pos++;
17362306a36Sopenharmony_ci	}
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ci	off += host->io_pos;
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci	return off;
17862306a36Sopenharmony_ci}
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_cistatic unsigned int tifm_ms_transfer_data(struct tifm_ms *host)
18162306a36Sopenharmony_ci{
18262306a36Sopenharmony_ci	struct tifm_dev *sock = host->dev;
18362306a36Sopenharmony_ci	unsigned int length;
18462306a36Sopenharmony_ci	unsigned int off;
18562306a36Sopenharmony_ci	unsigned int t_size, p_cnt;
18662306a36Sopenharmony_ci	unsigned char *buf;
18762306a36Sopenharmony_ci	struct page *pg;
18862306a36Sopenharmony_ci	unsigned long flags = 0;
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ci	if (host->req->long_data) {
19162306a36Sopenharmony_ci		length = host->req->sg.length - host->block_pos;
19262306a36Sopenharmony_ci		off = host->req->sg.offset + host->block_pos;
19362306a36Sopenharmony_ci	} else {
19462306a36Sopenharmony_ci		length = host->req->data_len - host->block_pos;
19562306a36Sopenharmony_ci		off = 0;
19662306a36Sopenharmony_ci	}
19762306a36Sopenharmony_ci	dev_dbg(&sock->dev, "fifo data transfer, %d, %d\n", length,
19862306a36Sopenharmony_ci		host->block_pos);
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci	while (length) {
20162306a36Sopenharmony_ci		unsigned int p_off;
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_ci		if (host->req->long_data) {
20462306a36Sopenharmony_ci			pg = nth_page(sg_page(&host->req->sg),
20562306a36Sopenharmony_ci				      off >> PAGE_SHIFT);
20662306a36Sopenharmony_ci			p_off = offset_in_page(off);
20762306a36Sopenharmony_ci			p_cnt = PAGE_SIZE - p_off;
20862306a36Sopenharmony_ci			p_cnt = min(p_cnt, length);
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ci			local_irq_save(flags);
21162306a36Sopenharmony_ci			buf = kmap_atomic(pg) + p_off;
21262306a36Sopenharmony_ci		} else {
21362306a36Sopenharmony_ci			buf = host->req->data + host->block_pos;
21462306a36Sopenharmony_ci			p_cnt = host->req->data_len - host->block_pos;
21562306a36Sopenharmony_ci		}
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_ci		t_size = host->req->data_dir == WRITE
21862306a36Sopenharmony_ci			 ? tifm_ms_write_data(host, buf, p_cnt)
21962306a36Sopenharmony_ci			 : tifm_ms_read_data(host, buf, p_cnt);
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci		if (host->req->long_data) {
22262306a36Sopenharmony_ci			kunmap_atomic(buf - p_off);
22362306a36Sopenharmony_ci			local_irq_restore(flags);
22462306a36Sopenharmony_ci		}
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_ci		if (!t_size)
22762306a36Sopenharmony_ci			break;
22862306a36Sopenharmony_ci		host->block_pos += t_size;
22962306a36Sopenharmony_ci		length -= t_size;
23062306a36Sopenharmony_ci		off += t_size;
23162306a36Sopenharmony_ci	}
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_ci	dev_dbg(&sock->dev, "fifo data transfer, %d remaining\n", length);
23462306a36Sopenharmony_ci	if (!length && (host->req->data_dir == WRITE)) {
23562306a36Sopenharmony_ci		if (host->io_pos) {
23662306a36Sopenharmony_ci			writel(TIFM_MS_SYS_FDIR
23762306a36Sopenharmony_ci			       | readl(sock->addr + SOCK_MS_SYSTEM),
23862306a36Sopenharmony_ci			       sock->addr + SOCK_MS_SYSTEM);
23962306a36Sopenharmony_ci			writel(host->io_word, sock->addr + SOCK_MS_DATA);
24062306a36Sopenharmony_ci		}
24162306a36Sopenharmony_ci		writel(TIFM_MS_SYS_FDIR
24262306a36Sopenharmony_ci		       | readl(sock->addr + SOCK_MS_SYSTEM),
24362306a36Sopenharmony_ci		       sock->addr + SOCK_MS_SYSTEM);
24462306a36Sopenharmony_ci		writel(0, sock->addr + SOCK_MS_DATA);
24562306a36Sopenharmony_ci	} else {
24662306a36Sopenharmony_ci		readl(sock->addr + SOCK_MS_DATA);
24762306a36Sopenharmony_ci	}
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_ci	return length;
25062306a36Sopenharmony_ci}
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_cistatic int tifm_ms_issue_cmd(struct tifm_ms *host)
25362306a36Sopenharmony_ci{
25462306a36Sopenharmony_ci	struct tifm_dev *sock = host->dev;
25562306a36Sopenharmony_ci	unsigned int data_len, cmd, sys_param;
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_ci	host->cmd_flags = 0;
25862306a36Sopenharmony_ci	host->block_pos = 0;
25962306a36Sopenharmony_ci	host->io_pos = 0;
26062306a36Sopenharmony_ci	host->io_word = 0;
26162306a36Sopenharmony_ci	host->cmd_flags = 0;
26262306a36Sopenharmony_ci
26362306a36Sopenharmony_ci	host->use_dma = !no_dma;
26462306a36Sopenharmony_ci
26562306a36Sopenharmony_ci	if (host->req->long_data) {
26662306a36Sopenharmony_ci		data_len = host->req->sg.length;
26762306a36Sopenharmony_ci		if (!is_power_of_2(data_len))
26862306a36Sopenharmony_ci			host->use_dma = 0;
26962306a36Sopenharmony_ci	} else {
27062306a36Sopenharmony_ci		data_len = host->req->data_len;
27162306a36Sopenharmony_ci		host->use_dma = 0;
27262306a36Sopenharmony_ci	}
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_ci	writel(TIFM_FIFO_INT_SETALL,
27562306a36Sopenharmony_ci	       sock->addr + SOCK_DMA_FIFO_INT_ENABLE_CLEAR);
27662306a36Sopenharmony_ci	writel(TIFM_FIFO_ENABLE,
27762306a36Sopenharmony_ci	       sock->addr + SOCK_FIFO_CONTROL);
27862306a36Sopenharmony_ci
27962306a36Sopenharmony_ci	if (host->use_dma) {
28062306a36Sopenharmony_ci		if (1 != tifm_map_sg(sock, &host->req->sg, 1,
28162306a36Sopenharmony_ci				     host->req->data_dir == READ
28262306a36Sopenharmony_ci				     ? DMA_FROM_DEVICE
28362306a36Sopenharmony_ci				     : DMA_TO_DEVICE)) {
28462306a36Sopenharmony_ci			host->req->error = -ENOMEM;
28562306a36Sopenharmony_ci			return host->req->error;
28662306a36Sopenharmony_ci		}
28762306a36Sopenharmony_ci		data_len = sg_dma_len(&host->req->sg);
28862306a36Sopenharmony_ci
28962306a36Sopenharmony_ci		writel(ilog2(data_len) - 2,
29062306a36Sopenharmony_ci		       sock->addr + SOCK_FIFO_PAGE_SIZE);
29162306a36Sopenharmony_ci		writel(TIFM_FIFO_INTMASK,
29262306a36Sopenharmony_ci		       sock->addr + SOCK_DMA_FIFO_INT_ENABLE_SET);
29362306a36Sopenharmony_ci		sys_param = TIFM_DMA_EN | (1 << 8);
29462306a36Sopenharmony_ci		if (host->req->data_dir == WRITE)
29562306a36Sopenharmony_ci			sys_param |= TIFM_DMA_TX;
29662306a36Sopenharmony_ci
29762306a36Sopenharmony_ci		writel(TIFM_FIFO_INTMASK,
29862306a36Sopenharmony_ci		       sock->addr + SOCK_DMA_FIFO_INT_ENABLE_SET);
29962306a36Sopenharmony_ci
30062306a36Sopenharmony_ci		writel(sg_dma_address(&host->req->sg),
30162306a36Sopenharmony_ci		       sock->addr + SOCK_DMA_ADDRESS);
30262306a36Sopenharmony_ci		writel(sys_param, sock->addr + SOCK_DMA_CONTROL);
30362306a36Sopenharmony_ci	} else {
30462306a36Sopenharmony_ci		writel(host->mode_mask | TIFM_MS_SYS_FIFO,
30562306a36Sopenharmony_ci		       sock->addr + SOCK_MS_SYSTEM);
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_ci		writel(TIFM_FIFO_MORE,
30862306a36Sopenharmony_ci		       sock->addr + SOCK_DMA_FIFO_INT_ENABLE_SET);
30962306a36Sopenharmony_ci	}
31062306a36Sopenharmony_ci
31162306a36Sopenharmony_ci	mod_timer(&host->timer, jiffies + host->timeout_jiffies);
31262306a36Sopenharmony_ci	writel(TIFM_CTRL_LED | readl(sock->addr + SOCK_CONTROL),
31362306a36Sopenharmony_ci	       sock->addr + SOCK_CONTROL);
31462306a36Sopenharmony_ci	host->req->error = 0;
31562306a36Sopenharmony_ci
31662306a36Sopenharmony_ci	sys_param = readl(sock->addr + SOCK_MS_SYSTEM);
31762306a36Sopenharmony_ci	sys_param |= TIFM_MS_SYS_INTCLR;
31862306a36Sopenharmony_ci
31962306a36Sopenharmony_ci	if (host->use_dma)
32062306a36Sopenharmony_ci		sys_param |= TIFM_MS_SYS_DMA;
32162306a36Sopenharmony_ci	else
32262306a36Sopenharmony_ci		sys_param &= ~TIFM_MS_SYS_DMA;
32362306a36Sopenharmony_ci
32462306a36Sopenharmony_ci	writel(sys_param, sock->addr + SOCK_MS_SYSTEM);
32562306a36Sopenharmony_ci
32662306a36Sopenharmony_ci	cmd = (host->req->tpc & 0xf) << 12;
32762306a36Sopenharmony_ci	cmd |= data_len;
32862306a36Sopenharmony_ci	writel(cmd, sock->addr + SOCK_MS_COMMAND);
32962306a36Sopenharmony_ci
33062306a36Sopenharmony_ci	dev_dbg(&sock->dev, "executing TPC %x, %x\n", cmd, sys_param);
33162306a36Sopenharmony_ci	return 0;
33262306a36Sopenharmony_ci}
33362306a36Sopenharmony_ci
33462306a36Sopenharmony_cistatic void tifm_ms_complete_cmd(struct tifm_ms *host)
33562306a36Sopenharmony_ci{
33662306a36Sopenharmony_ci	struct tifm_dev *sock = host->dev;
33762306a36Sopenharmony_ci	struct memstick_host *msh = tifm_get_drvdata(sock);
33862306a36Sopenharmony_ci	int rc;
33962306a36Sopenharmony_ci
34062306a36Sopenharmony_ci	del_timer(&host->timer);
34162306a36Sopenharmony_ci
34262306a36Sopenharmony_ci	host->req->int_reg = readl(sock->addr + SOCK_MS_STATUS) & 0xff;
34362306a36Sopenharmony_ci	host->req->int_reg = (host->req->int_reg & 1)
34462306a36Sopenharmony_ci			     | ((host->req->int_reg << 4) & 0xe0);
34562306a36Sopenharmony_ci
34662306a36Sopenharmony_ci	writel(TIFM_FIFO_INT_SETALL,
34762306a36Sopenharmony_ci	       sock->addr + SOCK_DMA_FIFO_INT_ENABLE_CLEAR);
34862306a36Sopenharmony_ci	writel(TIFM_DMA_RESET, sock->addr + SOCK_DMA_CONTROL);
34962306a36Sopenharmony_ci
35062306a36Sopenharmony_ci	if (host->use_dma) {
35162306a36Sopenharmony_ci		tifm_unmap_sg(sock, &host->req->sg, 1,
35262306a36Sopenharmony_ci			      host->req->data_dir == READ
35362306a36Sopenharmony_ci			      ? DMA_FROM_DEVICE
35462306a36Sopenharmony_ci			      : DMA_TO_DEVICE);
35562306a36Sopenharmony_ci	}
35662306a36Sopenharmony_ci
35762306a36Sopenharmony_ci	writel((~TIFM_CTRL_LED) & readl(sock->addr + SOCK_CONTROL),
35862306a36Sopenharmony_ci	       sock->addr + SOCK_CONTROL);
35962306a36Sopenharmony_ci
36062306a36Sopenharmony_ci	dev_dbg(&sock->dev, "TPC complete\n");
36162306a36Sopenharmony_ci	do {
36262306a36Sopenharmony_ci		rc = memstick_next_req(msh, &host->req);
36362306a36Sopenharmony_ci	} while (!rc && tifm_ms_issue_cmd(host));
36462306a36Sopenharmony_ci}
36562306a36Sopenharmony_ci
36662306a36Sopenharmony_cistatic int tifm_ms_check_status(struct tifm_ms *host)
36762306a36Sopenharmony_ci{
36862306a36Sopenharmony_ci	if (!host->req->error) {
36962306a36Sopenharmony_ci		if (!(host->cmd_flags & CMD_READY))
37062306a36Sopenharmony_ci			return 1;
37162306a36Sopenharmony_ci		if (!(host->cmd_flags & FIFO_READY))
37262306a36Sopenharmony_ci			return 1;
37362306a36Sopenharmony_ci		if (host->req->need_card_int
37462306a36Sopenharmony_ci		    && !(host->cmd_flags & CARD_INT))
37562306a36Sopenharmony_ci			return 1;
37662306a36Sopenharmony_ci	}
37762306a36Sopenharmony_ci	return 0;
37862306a36Sopenharmony_ci}
37962306a36Sopenharmony_ci
38062306a36Sopenharmony_ci/* Called from interrupt handler */
38162306a36Sopenharmony_cistatic void tifm_ms_data_event(struct tifm_dev *sock)
38262306a36Sopenharmony_ci{
38362306a36Sopenharmony_ci	struct tifm_ms *host;
38462306a36Sopenharmony_ci	unsigned int fifo_status = 0, host_status = 0;
38562306a36Sopenharmony_ci	int rc = 1;
38662306a36Sopenharmony_ci
38762306a36Sopenharmony_ci	spin_lock(&sock->lock);
38862306a36Sopenharmony_ci	host = memstick_priv((struct memstick_host *)tifm_get_drvdata(sock));
38962306a36Sopenharmony_ci	fifo_status = readl(sock->addr + SOCK_DMA_FIFO_STATUS);
39062306a36Sopenharmony_ci	host_status = readl(sock->addr + SOCK_MS_STATUS);
39162306a36Sopenharmony_ci	dev_dbg(&sock->dev,
39262306a36Sopenharmony_ci		"data event: fifo_status %x, host_status %x, flags %x\n",
39362306a36Sopenharmony_ci		fifo_status, host_status, host->cmd_flags);
39462306a36Sopenharmony_ci
39562306a36Sopenharmony_ci	if (host->req) {
39662306a36Sopenharmony_ci		if (host->use_dma && (fifo_status & 1)) {
39762306a36Sopenharmony_ci			host->cmd_flags |= FIFO_READY;
39862306a36Sopenharmony_ci			rc = tifm_ms_check_status(host);
39962306a36Sopenharmony_ci		}
40062306a36Sopenharmony_ci		if (!host->use_dma && (fifo_status & TIFM_FIFO_MORE)) {
40162306a36Sopenharmony_ci			if (!tifm_ms_transfer_data(host)) {
40262306a36Sopenharmony_ci				host->cmd_flags |= FIFO_READY;
40362306a36Sopenharmony_ci				rc = tifm_ms_check_status(host);
40462306a36Sopenharmony_ci			}
40562306a36Sopenharmony_ci		}
40662306a36Sopenharmony_ci	}
40762306a36Sopenharmony_ci
40862306a36Sopenharmony_ci	writel(fifo_status, sock->addr + SOCK_DMA_FIFO_STATUS);
40962306a36Sopenharmony_ci	if (!rc)
41062306a36Sopenharmony_ci		tifm_ms_complete_cmd(host);
41162306a36Sopenharmony_ci
41262306a36Sopenharmony_ci	spin_unlock(&sock->lock);
41362306a36Sopenharmony_ci}
41462306a36Sopenharmony_ci
41562306a36Sopenharmony_ci
41662306a36Sopenharmony_ci/* Called from interrupt handler */
41762306a36Sopenharmony_cistatic void tifm_ms_card_event(struct tifm_dev *sock)
41862306a36Sopenharmony_ci{
41962306a36Sopenharmony_ci	struct tifm_ms *host;
42062306a36Sopenharmony_ci	unsigned int host_status = 0;
42162306a36Sopenharmony_ci	int rc = 1;
42262306a36Sopenharmony_ci
42362306a36Sopenharmony_ci	spin_lock(&sock->lock);
42462306a36Sopenharmony_ci	host = memstick_priv((struct memstick_host *)tifm_get_drvdata(sock));
42562306a36Sopenharmony_ci	host_status = readl(sock->addr + SOCK_MS_STATUS);
42662306a36Sopenharmony_ci	dev_dbg(&sock->dev, "host event: host_status %x, flags %x\n",
42762306a36Sopenharmony_ci		host_status, host->cmd_flags);
42862306a36Sopenharmony_ci
42962306a36Sopenharmony_ci	if (host->req) {
43062306a36Sopenharmony_ci		if (host_status & TIFM_MS_STAT_TOE)
43162306a36Sopenharmony_ci			host->req->error = -ETIME;
43262306a36Sopenharmony_ci		else if (host_status & TIFM_MS_STAT_CRC)
43362306a36Sopenharmony_ci			host->req->error = -EILSEQ;
43462306a36Sopenharmony_ci
43562306a36Sopenharmony_ci		if (host_status & TIFM_MS_STAT_RDY)
43662306a36Sopenharmony_ci			host->cmd_flags |= CMD_READY;
43762306a36Sopenharmony_ci
43862306a36Sopenharmony_ci		if (host_status & TIFM_MS_STAT_MSINT)
43962306a36Sopenharmony_ci			host->cmd_flags |= CARD_INT;
44062306a36Sopenharmony_ci
44162306a36Sopenharmony_ci		rc = tifm_ms_check_status(host);
44262306a36Sopenharmony_ci
44362306a36Sopenharmony_ci	}
44462306a36Sopenharmony_ci
44562306a36Sopenharmony_ci	writel(TIFM_MS_SYS_INTCLR | readl(sock->addr + SOCK_MS_SYSTEM),
44662306a36Sopenharmony_ci	       sock->addr + SOCK_MS_SYSTEM);
44762306a36Sopenharmony_ci
44862306a36Sopenharmony_ci	if (!rc)
44962306a36Sopenharmony_ci		tifm_ms_complete_cmd(host);
45062306a36Sopenharmony_ci
45162306a36Sopenharmony_ci	spin_unlock(&sock->lock);
45262306a36Sopenharmony_ci	return;
45362306a36Sopenharmony_ci}
45462306a36Sopenharmony_ci
45562306a36Sopenharmony_cistatic void tifm_ms_req_tasklet(unsigned long data)
45662306a36Sopenharmony_ci{
45762306a36Sopenharmony_ci	struct memstick_host *msh = (struct memstick_host *)data;
45862306a36Sopenharmony_ci	struct tifm_ms *host = memstick_priv(msh);
45962306a36Sopenharmony_ci	struct tifm_dev *sock = host->dev;
46062306a36Sopenharmony_ci	unsigned long flags;
46162306a36Sopenharmony_ci	int rc;
46262306a36Sopenharmony_ci
46362306a36Sopenharmony_ci	spin_lock_irqsave(&sock->lock, flags);
46462306a36Sopenharmony_ci	if (!host->req) {
46562306a36Sopenharmony_ci		if (host->eject) {
46662306a36Sopenharmony_ci			do {
46762306a36Sopenharmony_ci				rc = memstick_next_req(msh, &host->req);
46862306a36Sopenharmony_ci				if (!rc)
46962306a36Sopenharmony_ci					host->req->error = -ETIME;
47062306a36Sopenharmony_ci			} while (!rc);
47162306a36Sopenharmony_ci			spin_unlock_irqrestore(&sock->lock, flags);
47262306a36Sopenharmony_ci			return;
47362306a36Sopenharmony_ci		}
47462306a36Sopenharmony_ci
47562306a36Sopenharmony_ci		do {
47662306a36Sopenharmony_ci			rc = memstick_next_req(msh, &host->req);
47762306a36Sopenharmony_ci		} while (!rc && tifm_ms_issue_cmd(host));
47862306a36Sopenharmony_ci	}
47962306a36Sopenharmony_ci	spin_unlock_irqrestore(&sock->lock, flags);
48062306a36Sopenharmony_ci}
48162306a36Sopenharmony_ci
48262306a36Sopenharmony_cistatic void tifm_ms_dummy_submit(struct memstick_host *msh)
48362306a36Sopenharmony_ci{
48462306a36Sopenharmony_ci	return;
48562306a36Sopenharmony_ci}
48662306a36Sopenharmony_ci
48762306a36Sopenharmony_cistatic void tifm_ms_submit_req(struct memstick_host *msh)
48862306a36Sopenharmony_ci{
48962306a36Sopenharmony_ci	struct tifm_ms *host = memstick_priv(msh);
49062306a36Sopenharmony_ci
49162306a36Sopenharmony_ci	tasklet_schedule(&host->notify);
49262306a36Sopenharmony_ci}
49362306a36Sopenharmony_ci
49462306a36Sopenharmony_cistatic int tifm_ms_set_param(struct memstick_host *msh,
49562306a36Sopenharmony_ci			     enum memstick_param param,
49662306a36Sopenharmony_ci			     int value)
49762306a36Sopenharmony_ci{
49862306a36Sopenharmony_ci	struct tifm_ms *host = memstick_priv(msh);
49962306a36Sopenharmony_ci	struct tifm_dev *sock = host->dev;
50062306a36Sopenharmony_ci
50162306a36Sopenharmony_ci	switch (param) {
50262306a36Sopenharmony_ci	case MEMSTICK_POWER:
50362306a36Sopenharmony_ci		/* also affected by media detection mechanism */
50462306a36Sopenharmony_ci		if (value == MEMSTICK_POWER_ON) {
50562306a36Sopenharmony_ci			host->mode_mask = TIFM_MS_SYS_SRAC | TIFM_MS_SYS_REI;
50662306a36Sopenharmony_ci			writel(TIFM_MS_SYS_RESET, sock->addr + SOCK_MS_SYSTEM);
50762306a36Sopenharmony_ci			writel(TIFM_MS_SYS_FCLR | TIFM_MS_SYS_INTCLR,
50862306a36Sopenharmony_ci			       sock->addr + SOCK_MS_SYSTEM);
50962306a36Sopenharmony_ci			writel(0xffffffff, sock->addr + SOCK_MS_STATUS);
51062306a36Sopenharmony_ci		} else if (value == MEMSTICK_POWER_OFF) {
51162306a36Sopenharmony_ci			writel(TIFM_MS_SYS_FCLR | TIFM_MS_SYS_INTCLR,
51262306a36Sopenharmony_ci			       sock->addr + SOCK_MS_SYSTEM);
51362306a36Sopenharmony_ci			writel(0xffffffff, sock->addr + SOCK_MS_STATUS);
51462306a36Sopenharmony_ci		} else
51562306a36Sopenharmony_ci			return -EINVAL;
51662306a36Sopenharmony_ci		break;
51762306a36Sopenharmony_ci	case MEMSTICK_INTERFACE:
51862306a36Sopenharmony_ci		if (value == MEMSTICK_SERIAL) {
51962306a36Sopenharmony_ci			host->mode_mask = TIFM_MS_SYS_SRAC | TIFM_MS_SYS_REI;
52062306a36Sopenharmony_ci			writel((~TIFM_CTRL_FAST_CLK)
52162306a36Sopenharmony_ci			       & readl(sock->addr + SOCK_CONTROL),
52262306a36Sopenharmony_ci			       sock->addr + SOCK_CONTROL);
52362306a36Sopenharmony_ci		} else if (value == MEMSTICK_PAR4) {
52462306a36Sopenharmony_ci			host->mode_mask = 0;
52562306a36Sopenharmony_ci			writel(TIFM_CTRL_FAST_CLK
52662306a36Sopenharmony_ci			       | readl(sock->addr + SOCK_CONTROL),
52762306a36Sopenharmony_ci			       sock->addr + SOCK_CONTROL);
52862306a36Sopenharmony_ci		} else
52962306a36Sopenharmony_ci			return -EINVAL;
53062306a36Sopenharmony_ci		break;
53162306a36Sopenharmony_ci	}
53262306a36Sopenharmony_ci
53362306a36Sopenharmony_ci	return 0;
53462306a36Sopenharmony_ci}
53562306a36Sopenharmony_ci
53662306a36Sopenharmony_cistatic void tifm_ms_abort(struct timer_list *t)
53762306a36Sopenharmony_ci{
53862306a36Sopenharmony_ci	struct tifm_ms *host = from_timer(host, t, timer);
53962306a36Sopenharmony_ci
54062306a36Sopenharmony_ci	dev_dbg(&host->dev->dev, "status %x\n",
54162306a36Sopenharmony_ci		readl(host->dev->addr + SOCK_MS_STATUS));
54262306a36Sopenharmony_ci	printk(KERN_ERR
54362306a36Sopenharmony_ci	       "%s : card failed to respond for a long period of time "
54462306a36Sopenharmony_ci	       "(%x, %x)\n",
54562306a36Sopenharmony_ci	       dev_name(&host->dev->dev), host->req ? host->req->tpc : 0,
54662306a36Sopenharmony_ci	       host->cmd_flags);
54762306a36Sopenharmony_ci
54862306a36Sopenharmony_ci	tifm_eject(host->dev);
54962306a36Sopenharmony_ci}
55062306a36Sopenharmony_ci
55162306a36Sopenharmony_cistatic int tifm_ms_probe(struct tifm_dev *sock)
55262306a36Sopenharmony_ci{
55362306a36Sopenharmony_ci	struct memstick_host *msh;
55462306a36Sopenharmony_ci	struct tifm_ms *host;
55562306a36Sopenharmony_ci	int rc = -EIO;
55662306a36Sopenharmony_ci
55762306a36Sopenharmony_ci	if (!(TIFM_SOCK_STATE_OCCUPIED
55862306a36Sopenharmony_ci	      & readl(sock->addr + SOCK_PRESENT_STATE))) {
55962306a36Sopenharmony_ci		printk(KERN_WARNING "%s : card gone, unexpectedly\n",
56062306a36Sopenharmony_ci		       dev_name(&sock->dev));
56162306a36Sopenharmony_ci		return rc;
56262306a36Sopenharmony_ci	}
56362306a36Sopenharmony_ci
56462306a36Sopenharmony_ci	msh = memstick_alloc_host(sizeof(struct tifm_ms), &sock->dev);
56562306a36Sopenharmony_ci	if (!msh)
56662306a36Sopenharmony_ci		return -ENOMEM;
56762306a36Sopenharmony_ci
56862306a36Sopenharmony_ci	host = memstick_priv(msh);
56962306a36Sopenharmony_ci	tifm_set_drvdata(sock, msh);
57062306a36Sopenharmony_ci	host->dev = sock;
57162306a36Sopenharmony_ci	host->timeout_jiffies = msecs_to_jiffies(1000);
57262306a36Sopenharmony_ci
57362306a36Sopenharmony_ci	timer_setup(&host->timer, tifm_ms_abort, 0);
57462306a36Sopenharmony_ci	tasklet_init(&host->notify, tifm_ms_req_tasklet, (unsigned long)msh);
57562306a36Sopenharmony_ci
57662306a36Sopenharmony_ci	msh->request = tifm_ms_submit_req;
57762306a36Sopenharmony_ci	msh->set_param = tifm_ms_set_param;
57862306a36Sopenharmony_ci	sock->card_event = tifm_ms_card_event;
57962306a36Sopenharmony_ci	sock->data_event = tifm_ms_data_event;
58062306a36Sopenharmony_ci	if (tifm_has_ms_pif(sock))
58162306a36Sopenharmony_ci		msh->caps |= MEMSTICK_CAP_PAR4;
58262306a36Sopenharmony_ci
58362306a36Sopenharmony_ci	rc = memstick_add_host(msh);
58462306a36Sopenharmony_ci	if (!rc)
58562306a36Sopenharmony_ci		return 0;
58662306a36Sopenharmony_ci
58762306a36Sopenharmony_ci	memstick_free_host(msh);
58862306a36Sopenharmony_ci	return rc;
58962306a36Sopenharmony_ci}
59062306a36Sopenharmony_ci
59162306a36Sopenharmony_cistatic void tifm_ms_remove(struct tifm_dev *sock)
59262306a36Sopenharmony_ci{
59362306a36Sopenharmony_ci	struct memstick_host *msh = tifm_get_drvdata(sock);
59462306a36Sopenharmony_ci	struct tifm_ms *host = memstick_priv(msh);
59562306a36Sopenharmony_ci	int rc = 0;
59662306a36Sopenharmony_ci	unsigned long flags;
59762306a36Sopenharmony_ci
59862306a36Sopenharmony_ci	msh->request = tifm_ms_dummy_submit;
59962306a36Sopenharmony_ci	tasklet_kill(&host->notify);
60062306a36Sopenharmony_ci	spin_lock_irqsave(&sock->lock, flags);
60162306a36Sopenharmony_ci	host->eject = 1;
60262306a36Sopenharmony_ci	if (host->req) {
60362306a36Sopenharmony_ci		del_timer(&host->timer);
60462306a36Sopenharmony_ci		writel(TIFM_FIFO_INT_SETALL,
60562306a36Sopenharmony_ci		       sock->addr + SOCK_DMA_FIFO_INT_ENABLE_CLEAR);
60662306a36Sopenharmony_ci		writel(TIFM_DMA_RESET, sock->addr + SOCK_DMA_CONTROL);
60762306a36Sopenharmony_ci		if (host->use_dma)
60862306a36Sopenharmony_ci			tifm_unmap_sg(sock, &host->req->sg, 1,
60962306a36Sopenharmony_ci				      host->req->data_dir == READ
61062306a36Sopenharmony_ci				      ? DMA_TO_DEVICE
61162306a36Sopenharmony_ci				      : DMA_FROM_DEVICE);
61262306a36Sopenharmony_ci		host->req->error = -ETIME;
61362306a36Sopenharmony_ci
61462306a36Sopenharmony_ci		do {
61562306a36Sopenharmony_ci			rc = memstick_next_req(msh, &host->req);
61662306a36Sopenharmony_ci			if (!rc)
61762306a36Sopenharmony_ci				host->req->error = -ETIME;
61862306a36Sopenharmony_ci		} while (!rc);
61962306a36Sopenharmony_ci	}
62062306a36Sopenharmony_ci	spin_unlock_irqrestore(&sock->lock, flags);
62162306a36Sopenharmony_ci
62262306a36Sopenharmony_ci	memstick_remove_host(msh);
62362306a36Sopenharmony_ci	memstick_free_host(msh);
62462306a36Sopenharmony_ci}
62562306a36Sopenharmony_ci
62662306a36Sopenharmony_ci#ifdef CONFIG_PM
62762306a36Sopenharmony_ci
62862306a36Sopenharmony_cistatic int tifm_ms_suspend(struct tifm_dev *sock, pm_message_t state)
62962306a36Sopenharmony_ci{
63062306a36Sopenharmony_ci	struct memstick_host *msh = tifm_get_drvdata(sock);
63162306a36Sopenharmony_ci
63262306a36Sopenharmony_ci	memstick_suspend_host(msh);
63362306a36Sopenharmony_ci	return 0;
63462306a36Sopenharmony_ci}
63562306a36Sopenharmony_ci
63662306a36Sopenharmony_cistatic int tifm_ms_resume(struct tifm_dev *sock)
63762306a36Sopenharmony_ci{
63862306a36Sopenharmony_ci	struct memstick_host *msh = tifm_get_drvdata(sock);
63962306a36Sopenharmony_ci
64062306a36Sopenharmony_ci	memstick_resume_host(msh);
64162306a36Sopenharmony_ci	return 0;
64262306a36Sopenharmony_ci}
64362306a36Sopenharmony_ci
64462306a36Sopenharmony_ci#else
64562306a36Sopenharmony_ci
64662306a36Sopenharmony_ci#define tifm_ms_suspend NULL
64762306a36Sopenharmony_ci#define tifm_ms_resume NULL
64862306a36Sopenharmony_ci
64962306a36Sopenharmony_ci#endif /* CONFIG_PM */
65062306a36Sopenharmony_ci
65162306a36Sopenharmony_cistatic struct tifm_device_id tifm_ms_id_tbl[] = {
65262306a36Sopenharmony_ci	{ TIFM_TYPE_MS }, { 0 }
65362306a36Sopenharmony_ci};
65462306a36Sopenharmony_ci
65562306a36Sopenharmony_cistatic struct tifm_driver tifm_ms_driver = {
65662306a36Sopenharmony_ci	.driver = {
65762306a36Sopenharmony_ci		.name  = DRIVER_NAME,
65862306a36Sopenharmony_ci		.owner = THIS_MODULE
65962306a36Sopenharmony_ci	},
66062306a36Sopenharmony_ci	.id_table = tifm_ms_id_tbl,
66162306a36Sopenharmony_ci	.probe    = tifm_ms_probe,
66262306a36Sopenharmony_ci	.remove   = tifm_ms_remove,
66362306a36Sopenharmony_ci	.suspend  = tifm_ms_suspend,
66462306a36Sopenharmony_ci	.resume   = tifm_ms_resume
66562306a36Sopenharmony_ci};
66662306a36Sopenharmony_ci
66762306a36Sopenharmony_cistatic int __init tifm_ms_init(void)
66862306a36Sopenharmony_ci{
66962306a36Sopenharmony_ci	return tifm_register_driver(&tifm_ms_driver);
67062306a36Sopenharmony_ci}
67162306a36Sopenharmony_ci
67262306a36Sopenharmony_cistatic void __exit tifm_ms_exit(void)
67362306a36Sopenharmony_ci{
67462306a36Sopenharmony_ci	tifm_unregister_driver(&tifm_ms_driver);
67562306a36Sopenharmony_ci}
67662306a36Sopenharmony_ci
67762306a36Sopenharmony_ciMODULE_AUTHOR("Alex Dubov");
67862306a36Sopenharmony_ciMODULE_DESCRIPTION("TI FlashMedia MemoryStick driver");
67962306a36Sopenharmony_ciMODULE_LICENSE("GPL");
68062306a36Sopenharmony_ciMODULE_DEVICE_TABLE(tifm, tifm_ms_id_tbl);
68162306a36Sopenharmony_ci
68262306a36Sopenharmony_cimodule_init(tifm_ms_init);
68362306a36Sopenharmony_cimodule_exit(tifm_ms_exit);
684