![Thomas Gleixner](/assets/img/avatar_default.png)
Based on 1 normalized pattern(s): 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 extracted by the scancode license scanner the SPDX license identifier GPL-2.0-or-later has been chosen to replace the boilerplate/reference in 13 file(s). Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Reviewed-by: Steve Winslow <swinslow@gmail.com> Reviewed-by: Jilayne Lovejoy <opensource@jilayne.com> Reviewed-by: Kate Stewart <kstewart@linuxfoundation.org> Reviewed-by: Allison Randal <allison@lohutok.net> Cc: linux-spdx@vger.kernel.org Link: https://lkml.kernel.org/r/20190519154042.236620792@linutronix.de Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
644 lines
15 KiB
C
644 lines
15 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/* Realtek PCI-Express Memstick Card Interface driver
|
|
*
|
|
* Copyright(c) 2009-2013 Realtek Semiconductor Corp. All rights reserved.
|
|
*
|
|
* 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/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 = {
|
|
.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");
|