cregit-Linux how code gets into the kernel

Release 4.11 drivers/memstick/host/tifm_ms.c

/*
 *  TI FlashMedia driver
 *
 *  Copyright (C) 2007 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.
 *
 * Special thanks to Carlos Corbacho for providing various MemoryStick cards
 * that made this driver possible.
 *
 */

#include <linux/tifm.h>
#include <linux/memstick.h>
#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 bool no_dma;
module_param(no_dma, bool, 0644);

/*
 * Some control bits of TIFM appear to conform to Sony's reference design,
 * so I'm just assuming they all are.
 */


#define TIFM_MS_STAT_DRQ     0x04000

#define TIFM_MS_STAT_MSINT   0x02000

#define TIFM_MS_STAT_RDY     0x01000

#define TIFM_MS_STAT_CRC     0x00200

#define TIFM_MS_STAT_TOE     0x00100

#define TIFM_MS_STAT_EMP     0x00020

#define TIFM_MS_STAT_FUL     0x00010

#define TIFM_MS_STAT_CED     0x00008

#define TIFM_MS_STAT_ERR     0x00004

#define TIFM_MS_STAT_BRQ     0x00002

#define TIFM_MS_STAT_CNK     0x00001


#define TIFM_MS_SYS_DMA      0x10000

#define TIFM_MS_SYS_RESET    0x08000

#define TIFM_MS_SYS_SRAC     0x04000

#define TIFM_MS_SYS_INTEN    0x02000

#define TIFM_MS_SYS_NOCRC    0x01000

#define TIFM_MS_SYS_INTCLR   0x00800

#define TIFM_MS_SYS_MSIEN    0x00400

#define TIFM_MS_SYS_FCLR     0x00200

#define TIFM_MS_SYS_FDIR     0x00100

#define TIFM_MS_SYS_DAM      0x00080

#define TIFM_MS_SYS_DRM      0x00040

#define TIFM_MS_SYS_DRQSL    0x00020

#define TIFM_MS_SYS_REI      0x00010

#define TIFM_MS_SYS_REO      0x00008

#define TIFM_MS_SYS_BSY_MASK 0x00007


#define TIFM_MS_SYS_FIFO     (TIFM_MS_SYS_INTEN | TIFM_MS_SYS_MSIEN \
                              | TIFM_MS_SYS_FCLR | TIFM_MS_SYS_BSY_MASK)

/* Hardware flags */
enum {
	
CMD_READY  = 0x01,
	
FIFO_READY = 0x02,
	
CARD_INT   = 0x04
};


struct tifm_ms {
	
struct tifm_dev         *dev;
	
struct timer_list       timer;
	
struct memstick_request *req;
	
struct tasklet_struct   notify;
	
unsigned int            mode_mask;
	
unsigned int            block_pos;
	
unsigned long           timeout_jiffies;
	
unsigned char           eject:1,
				
use_dma:1;
	
unsigned char           cmd_flags;
	
unsigned char           io_pos;
	
unsigned int            io_word;
};


static unsigned int tifm_ms_read_data(struct tifm_ms *host, unsigned char *buf, unsigned int length) { struct tifm_dev *sock = host->dev; unsigned int off = 0; while (host->io_pos && length) { buf[off++] = host->io_word & 0xff; host->io_word >>= 8; length--; host->io_pos--; } if (!length) return off; while (!(TIFM_MS_STAT_EMP & readl(sock->addr + SOCK_MS_STATUS))) { if (length < 4) break; *(unsigned int *)(buf + off) = __raw_readl(sock->addr + SOCK_MS_DATA); length -= 4; off += 4; } if (length && !(TIFM_MS_STAT_EMP & readl(sock->addr + SOCK_MS_STATUS))) { host->io_word = readl(sock->addr + SOCK_MS_DATA); for (host->io_pos = 4; host->io_pos; --host->io_pos) { buf[off++] = host->io_word & 0xff; host->io_word >>= 8; length--; if (!length) break; } } return off; }

Contributors

PersonTokensPropCommitsCommitProp
Alex Dubov216100.00%2100.00%
Total216100.00%2100.00%


static unsigned int tifm_ms_write_data(struct tifm_ms *host, unsigned char *buf, unsigned int length) { struct tifm_dev *sock = host->dev; unsigned int off = 0; if (host->io_pos) { while (host->io_pos < 4 && length) { host->io_word |= buf[off++] << (host->io_pos * 8); host->io_pos++; length--; } } if (host->io_pos == 4 && !(TIFM_MS_STAT_FUL & readl(sock->addr + SOCK_MS_STATUS))) { writel(TIFM_MS_SYS_FDIR | readl(sock->addr + SOCK_MS_SYSTEM), sock->addr + SOCK_MS_SYSTEM); writel(host->io_word, sock->addr + SOCK_MS_DATA); host->io_pos = 0; host->io_word = 0; } else if (host->io_pos) { return off; } if (!length) return off; while (!(TIFM_MS_STAT_FUL & readl(sock->addr + SOCK_MS_STATUS))) { if (length < 4) break; writel(TIFM_MS_SYS_FDIR | readl(sock->addr + SOCK_MS_SYSTEM), sock->addr + SOCK_MS_SYSTEM); __raw_writel(*(unsigned int *)(buf + off), sock->addr + SOCK_MS_DATA); length -= 4; off += 4; } switch (length) { case 3: host->io_word |= buf[off + 2] << 16; host->io_pos++; case 2: host->io_word |= buf[off + 1] << 8; host->io_pos++; case 1: host->io_word |= buf[off]; host->io_pos++; } off += host->io_pos; return off; }

Contributors

PersonTokensPropCommitsCommitProp
Alex Dubov319100.00%2100.00%
Total319100.00%2100.00%


static unsigned int tifm_ms_transfer_data(struct tifm_ms *host) { struct tifm_dev *sock = host->dev; unsigned int length; unsigned int off; unsigned int t_size, p_cnt; unsigned char *buf; struct page *pg; unsigned long flags = 0; if (host->req->long_data) { length = host->req->sg.length - host->block_pos; off = host->req->sg.offset + host->block_pos; } else { length = host->req->data_len - host->block_pos; off = 0; } dev_dbg(&sock->dev, "fifo data transfer, %d, %d\n", length, host->block_pos); while (length) { unsigned int uninitialized_var(p_off); if (host->req->long_data) { pg = nth_page(sg_page(&host->req->sg), off >> PAGE_SHIFT); p_off = offset_in_page(off); p_cnt = PAGE_SIZE - p_off; p_cnt = min(p_cnt, length); local_irq_save(flags); buf = kmap_atomic(pg) + p_off; } else { buf = host->req->data + host->block_pos; p_cnt = host->req->data_len - host->block_pos; } t_size = host->req->data_dir == WRITE ? tifm_ms_write_data(host, buf, p_cnt) : tifm_ms_read_data(host, buf, p_cnt); if (host->req->long_data) { kunmap_atomic(buf - p_off); local_irq_restore(flags); } if (!t_size) break; host->block_pos += t_size; length -= t_size; off += t_size; } dev_dbg(&sock->dev, "fifo data transfer, %d remaining\n", length); if (!length && (host->req->data_dir == WRITE)) { if (host->io_pos) { writel(TIFM_MS_SYS_FDIR | readl(sock->addr + SOCK_MS_SYSTEM), sock->addr + SOCK_MS_SYSTEM); writel(host->io_word, sock->addr + SOCK_MS_DATA); } writel(TIFM_MS_SYS_FDIR | readl(sock->addr + SOCK_MS_SYSTEM), sock->addr + SOCK_MS_SYSTEM); writel(0, sock->addr + SOCK_MS_DATA); } else { readl(sock->addr + SOCK_MS_DATA); } return length; }

Contributors

PersonTokensPropCommitsCommitProp
Alex Dubov40898.31%266.67%
Andrew Morton71.69%133.33%
Total415100.00%3100.00%


static int tifm_ms_issue_cmd(struct tifm_ms *host) { struct tifm_dev *sock = host->dev; unsigned char *data; unsigned int data_len, cmd, sys_param; host->cmd_flags = 0; host->block_pos = 0; host->io_pos = 0; host->io_word = 0; host->cmd_flags = 0; data = host->req->data; host->use_dma = !no_dma; if (host->req->long_data) { data_len = host->req->sg.length; if (!is_power_of_2(data_len)) host->use_dma = 0; } else { data_len = host->req->data_len; host->use_dma = 0; } writel(TIFM_FIFO_INT_SETALL, sock->addr + SOCK_DMA_FIFO_INT_ENABLE_CLEAR); writel(TIFM_FIFO_ENABLE, sock->addr + SOCK_FIFO_CONTROL); if (host->use_dma) { if (1 != tifm_map_sg(sock, &host->req->sg, 1, host->req->data_dir == READ ? PCI_DMA_FROMDEVICE : PCI_DMA_TODEVICE)) { host->req->error = -ENOMEM; return host->req->error; } data_len = sg_dma_len(&host->req->sg); writel(ilog2(data_len) - 2, sock->addr + SOCK_FIFO_PAGE_SIZE); writel(TIFM_FIFO_INTMASK, sock->addr + SOCK_DMA_FIFO_INT_ENABLE_SET); sys_param = TIFM_DMA_EN | (1 << 8); if (host->req->data_dir == WRITE) sys_param |= TIFM_DMA_TX; writel(TIFM_FIFO_INTMASK, sock->addr + SOCK_DMA_FIFO_INT_ENABLE_SET); writel(sg_dma_address(&host->req->sg), sock->addr + SOCK_DMA_ADDRESS); writel(sys_param, sock->addr + SOCK_DMA_CONTROL); } else { writel(host->mode_mask | TIFM_MS_SYS_FIFO, sock->addr + SOCK_MS_SYSTEM); writel(TIFM_FIFO_MORE, sock->addr + SOCK_DMA_FIFO_INT_ENABLE_SET); } mod_timer(&host->timer, jiffies + host->timeout_jiffies); writel(TIFM_CTRL_LED | readl(sock->addr + SOCK_CONTROL), sock->addr + SOCK_CONTROL); host->req->error = 0; sys_param = readl(sock->addr + SOCK_MS_SYSTEM); sys_param |= TIFM_MS_SYS_INTCLR; if (host->use_dma) sys_param |= TIFM_MS_SYS_DMA; else sys_param &= ~TIFM_MS_SYS_DMA; writel(sys_param, sock->addr + SOCK_MS_SYSTEM); cmd = (host->req->tpc & 0xf) << 12; cmd |= data_len; writel(cmd, sock->addr + SOCK_MS_COMMAND); dev_dbg(&sock->dev, "executing TPC %x, %x\n", cmd, sys_param); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Alex Dubov46999.79%375.00%
Al Viro10.21%125.00%
Total470100.00%4100.00%


static void tifm_ms_complete_cmd(struct tifm_ms *host) { struct tifm_dev *sock = host->dev; struct memstick_host *msh = tifm_get_drvdata(sock); int rc; del_timer(&host->timer); host->req->int_reg = readl(sock->addr + SOCK_MS_STATUS) & 0xff; host->req->int_reg = (host->req->int_reg & 1) | ((host->req->int_reg << 4) & 0xe0); writel(TIFM_FIFO_INT_SETALL, sock->addr + SOCK_DMA_FIFO_INT_ENABLE_CLEAR); writel(TIFM_DMA_RESET, sock->addr + SOCK_DMA_CONTROL); if (host->use_dma) { tifm_unmap_sg(sock, &host->req->sg, 1, host->req->data_dir == READ ? PCI_DMA_FROMDEVICE : PCI_DMA_TODEVICE); } writel((~TIFM_CTRL_LED) & readl(sock->addr + SOCK_CONTROL), sock->addr + SOCK_CONTROL); dev_dbg(&sock->dev, "TPC complete\n"); do { rc = memstick_next_req(msh, &host->req); } while (!rc && tifm_ms_issue_cmd(host)); }

Contributors

PersonTokensPropCommitsCommitProp
Alex Dubov203100.00%3100.00%
Total203100.00%3100.00%


static int tifm_ms_check_status(struct tifm_ms *host) { if (!host->req->error) { if (!(host->cmd_flags & CMD_READY)) return 1; if (!(host->cmd_flags & FIFO_READY)) return 1; if (host->req->need_card_int && !(host->cmd_flags & CARD_INT)) return 1; } return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Alex Dubov73100.00%2100.00%
Total73100.00%2100.00%

/* Called from interrupt handler */
static void tifm_ms_data_event(struct tifm_dev *sock) { struct tifm_ms *host; unsigned int fifo_status = 0, host_status = 0; int rc = 1; spin_lock(&sock->lock); host = memstick_priv((struct memstick_host *)tifm_get_drvdata(sock)); fifo_status = readl(sock->addr + SOCK_DMA_FIFO_STATUS); host_status = readl(sock->addr + SOCK_MS_STATUS); dev_dbg(&sock->dev, "data event: fifo_status %x, host_status %x, flags %x\n", fifo_status, host_status, host->cmd_flags); if (host->req) { if (host->use_dma && (fifo_status & 1)) { host->cmd_flags |= FIFO_READY; rc = tifm_ms_check_status(host); } if (!host->use_dma && (fifo_status & TIFM_FIFO_MORE)) { if (!tifm_ms_transfer_data(host)) { host->cmd_flags |= FIFO_READY; rc = tifm_ms_check_status(host); } } } writel(fifo_status, sock->addr + SOCK_DMA_FIFO_STATUS); if (!rc) tifm_ms_complete_cmd(host); spin_unlock(&sock->lock); }

Contributors

PersonTokensPropCommitsCommitProp
Alex Dubov196100.00%2100.00%
Total196100.00%2100.00%

/* Called from interrupt handler */
static void tifm_ms_card_event(struct tifm_dev *sock) { struct tifm_ms *host; unsigned int host_status = 0; int rc = 1; spin_lock(&sock->lock); host = memstick_priv((struct memstick_host *)tifm_get_drvdata(sock)); host_status = readl(sock->addr + SOCK_MS_STATUS); dev_dbg(&sock->dev, "host event: host_status %x, flags %x\n", host_status, host->cmd_flags); if (host->req) { if (host_status & TIFM_MS_STAT_TOE) host->req->error = -ETIME; else if (host_status & TIFM_MS_STAT_CRC) host->req->error = -EILSEQ; if (host_status & TIFM_MS_STAT_RDY) host->cmd_flags |= CMD_READY; if (host_status & TIFM_MS_STAT_MSINT) host->cmd_flags |= CARD_INT; rc = tifm_ms_check_status(host); } writel(TIFM_MS_SYS_INTCLR | readl(sock->addr + SOCK_MS_SYSTEM), sock->addr + SOCK_MS_SYSTEM); if (!rc) tifm_ms_complete_cmd(host); spin_unlock(&sock->lock); return; }

Contributors

PersonTokensPropCommitsCommitProp
Alex Dubov186100.00%2100.00%
Total186100.00%2100.00%


static void tifm_ms_req_tasklet(unsigned long data) { struct memstick_host *msh = (struct memstick_host *)data; struct tifm_ms *host = memstick_priv(msh); struct tifm_dev *sock = host->dev; unsigned long flags; int rc; spin_lock_irqsave(&sock->lock, flags); if (!host->req) { if (host->eject) { do { rc = memstick_next_req(msh, &host->req); if (!rc) host->req->error = -ETIME; } while (!rc); spin_unlock_irqrestore(&sock->lock, flags); return; } do { rc = memstick_next_req(msh, &host->req); } while (!rc && tifm_ms_issue_cmd(host)); } spin_unlock_irqrestore(&sock->lock, flags); }

Contributors

PersonTokensPropCommitsCommitProp
Alex Dubov157100.00%2100.00%
Total157100.00%2100.00%


static void tifm_ms_dummy_submit(struct memstick_host *msh) { return; }

Contributors

PersonTokensPropCommitsCommitProp
Alex Dubov12100.00%2100.00%
Total12100.00%2100.00%


static void tifm_ms_submit_req(struct memstick_host *msh) { struct tifm_ms *host = memstick_priv(msh); tasklet_schedule(&host->notify); }

Contributors

PersonTokensPropCommitsCommitProp
Alex Dubov29100.00%1100.00%
Total29100.00%1100.00%


static int tifm_ms_set_param(struct memstick_host *msh, enum memstick_param param, int value) { struct tifm_ms *host = memstick_priv(msh); struct tifm_dev *sock = host->dev; switch (param) { case MEMSTICK_POWER: /* also affected by media detection mechanism */ if (value == MEMSTICK_POWER_ON) { host->mode_mask = TIFM_MS_SYS_SRAC | TIFM_MS_SYS_REI; writel(TIFM_MS_SYS_RESET, sock->addr + SOCK_MS_SYSTEM); writel(TIFM_MS_SYS_FCLR | TIFM_MS_SYS_INTCLR, sock->addr + SOCK_MS_SYSTEM); writel(0xffffffff, sock->addr + SOCK_MS_STATUS); } else if (value == MEMSTICK_POWER_OFF) { writel(TIFM_MS_SYS_FCLR | TIFM_MS_SYS_INTCLR, sock->addr + SOCK_MS_SYSTEM); writel(0xffffffff, sock->addr + SOCK_MS_STATUS); } else return -EINVAL; break; case MEMSTICK_INTERFACE: if (value == MEMSTICK_SERIAL) { host->mode_mask = TIFM_MS_SYS_SRAC | TIFM_MS_SYS_REI; writel((~TIFM_CTRL_FAST_CLK) & readl(sock->addr + SOCK_CONTROL), sock->addr + SOCK_CONTROL); } else if (value == MEMSTICK_PAR4) { host->mode_mask = 0; writel(TIFM_CTRL_FAST_CLK | readl(sock->addr + SOCK_CONTROL), sock->addr + SOCK_CONTROL); } else return -EINVAL; break; }; return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Alex Dubov224100.00%5100.00%
Total224100.00%5100.00%


static void tifm_ms_abort(unsigned long data) { struct tifm_ms *host = (struct tifm_ms *)data; dev_dbg(&host->dev->dev, "status %x\n", readl(host->dev->addr + SOCK_MS_STATUS)); printk(KERN_ERR "%s : card failed to respond for a long period of time " "(%x, %x)\n", dev_name(&host->dev->dev), host->req ? host->req->tpc : 0, host->cmd_flags); tifm_eject(host->dev); }

Contributors

PersonTokensPropCommitsCommitProp
Alex Dubov8195.29%150.00%
Kay Sievers44.71%150.00%
Total85100.00%2100.00%


static int tifm_ms_probe(struct tifm_dev *sock) { struct memstick_host *msh; struct tifm_ms *host; int rc = -EIO; if (!(TIFM_SOCK_STATE_OCCUPIED & readl(sock->addr + SOCK_PRESENT_STATE))) { printk(KERN_WARNING "%s : card gone, unexpectedly\n", dev_name(&sock->dev)); return rc; } msh = memstick_alloc_host(sizeof(struct tifm_ms), &sock->dev); if (!msh) return -ENOMEM; host = memstick_priv(msh); tifm_set_drvdata(sock, msh); host->dev = sock; host->timeout_jiffies = msecs_to_jiffies(1000); setup_timer(&host->timer, tifm_ms_abort, (unsigned long)host); tasklet_init(&host->notify, tifm_ms_req_tasklet, (unsigned long)msh); msh->request = tifm_ms_submit_req; msh->set_param = tifm_ms_set_param; sock->card_event = tifm_ms_card_event; sock->data_event = tifm_ms_data_event; if (tifm_has_ms_pif(sock)) msh->caps |= MEMSTICK_CAP_PAR4; rc = memstick_add_host(msh); if (!rc) return 0; memstick_free_host(msh); return rc; }

Contributors

PersonTokensPropCommitsCommitProp
Alex Dubov20498.08%375.00%
Kay Sievers41.92%125.00%
Total208100.00%4100.00%


static void tifm_ms_remove(struct tifm_dev *sock) { struct memstick_host *msh = tifm_get_drvdata(sock); struct tifm_ms *host = memstick_priv(msh); int rc = 0; unsigned long flags; msh->request = tifm_ms_dummy_submit; tasklet_kill(&host->notify); spin_lock_irqsave(&sock->lock, flags); host->eject = 1; if (host->req) { del_timer(&host->timer); writel(TIFM_FIFO_INT_SETALL, sock->addr + SOCK_DMA_FIFO_INT_ENABLE_CLEAR); writel(TIFM_DMA_RESET, sock->addr + SOCK_DMA_CONTROL); if (host->use_dma) tifm_unmap_sg(sock, &host->req->sg, 1, host->req->data_dir == READ ? PCI_DMA_TODEVICE : PCI_DMA_FROMDEVICE); host->req->error = -ETIME; do { rc = memstick_next_req(msh, &host->req); if (!rc) host->req->error = -ETIME; } while (!rc); } spin_unlock_irqrestore(&sock->lock, flags); memstick_remove_host(msh); memstick_free_host(msh); }

Contributors

PersonTokensPropCommitsCommitProp
Alex Dubov204100.00%3100.00%
Total204100.00%3100.00%

#ifdef CONFIG_PM
static int tifm_ms_suspend(struct tifm_dev *sock, pm_message_t state) { struct memstick_host *msh = tifm_get_drvdata(sock); memstick_suspend_host(msh); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Alex Dubov32100.00%2100.00%
Total32100.00%2100.00%


static int tifm_ms_resume(struct tifm_dev *sock) { struct memstick_host *msh = tifm_get_drvdata(sock); memstick_resume_host(msh); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Alex Dubov29100.00%2100.00%
Total29100.00%2100.00%

#else #define tifm_ms_suspend NULL #define tifm_ms_resume NULL #endif /* CONFIG_PM */ static struct tifm_device_id tifm_ms_id_tbl[] = { { TIFM_TYPE_MS }, { 0 } }; static struct tifm_driver tifm_ms_driver = { .driver = { .name = DRIVER_NAME, .owner = THIS_MODULE }, .id_table = tifm_ms_id_tbl, .probe = tifm_ms_probe, .remove = tifm_ms_remove, .suspend = tifm_ms_suspend, .resume = tifm_ms_resume };
static int __init tifm_ms_init(void) { return tifm_register_driver(&tifm_ms_driver); }

Contributors

PersonTokensPropCommitsCommitProp
Alex Dubov16100.00%1100.00%
Total16100.00%1100.00%


static void __exit tifm_ms_exit(void) { tifm_unregister_driver(&tifm_ms_driver); }

Contributors

PersonTokensPropCommitsCommitProp
Alex Dubov15100.00%1100.00%
Total15100.00%1100.00%

MODULE_AUTHOR("Alex Dubov"); MODULE_DESCRIPTION("TI FlashMedia MemoryStick driver"); MODULE_LICENSE("GPL"); MODULE_DEVICE_TABLE(tifm, tifm_ms_id_tbl); module_init(tifm_ms_init); module_exit(tifm_ms_exit);

Overall Contributors

PersonTokensPropCommitsCommitProp
Alex Dubov340399.42%861.54%
Kay Sievers80.23%17.69%
Andrew Morton70.20%17.69%
Paul Gortmaker30.09%17.69%
Al Viro10.03%17.69%
Rusty Russell10.03%17.69%
Total3423100.00%13100.00%
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with cregit.