2011-07-19 09:10:35 +00:00
|
|
|
/* Driver for Realtek RTS51xx USB card reader
|
|
|
|
*
|
|
|
|
* Copyright(c) 2009 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:
|
|
|
|
* wwang (wei_wang@realsil.com.cn)
|
|
|
|
* No. 450, Shenhu Road, Suzhou Industry Park, Suzhou, China
|
|
|
|
* Maintainer:
|
|
|
|
* Edwin Rong (edwin_rong@realsil.com.cn)
|
|
|
|
* No. 450, Shenhu Road, Suzhou Industry Park, Suzhou, China
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/blkdev.h>
|
|
|
|
#include <linux/kthread.h>
|
|
|
|
#include <linux/sched.h>
|
|
|
|
#include <linux/workqueue.h>
|
|
|
|
#include <linux/errno.h>
|
|
|
|
#include <linux/freezer.h>
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/slab.h>
|
|
|
|
#include <linux/mutex.h>
|
|
|
|
#include <linux/utsname.h>
|
|
|
|
#include <linux/usb.h>
|
|
|
|
|
|
|
|
#include <scsi/scsi.h>
|
|
|
|
#include <scsi/scsi_cmnd.h>
|
|
|
|
#include <scsi/scsi_device.h>
|
|
|
|
#include <scsi/scsi_devinfo.h>
|
|
|
|
#include <scsi/scsi_eh.h>
|
|
|
|
#include <scsi/scsi_host.h>
|
|
|
|
|
|
|
|
#include "debug.h"
|
|
|
|
#include "ms.h"
|
|
|
|
#include "rts51x.h"
|
|
|
|
#include "rts51x_chip.h"
|
|
|
|
#include "rts51x_card.h"
|
|
|
|
#include "rts51x_scsi.h"
|
|
|
|
#include "rts51x_transport.h"
|
|
|
|
#include "rts51x_fop.h"
|
|
|
|
|
|
|
|
MODULE_DESCRIPTION(RTS51X_DESC);
|
|
|
|
MODULE_LICENSE("GPL");
|
|
|
|
MODULE_VERSION(DRIVER_VERSION);
|
|
|
|
|
|
|
|
static int auto_delink_en;
|
|
|
|
module_param(auto_delink_en, int, S_IRUGO | S_IWUSR);
|
|
|
|
MODULE_PARM_DESC(auto_delink_en, "enable auto delink");
|
|
|
|
|
|
|
|
static int ss_en;
|
|
|
|
module_param(ss_en, int, S_IRUGO | S_IWUSR);
|
|
|
|
MODULE_PARM_DESC(ss_en, "enable selective suspend");
|
|
|
|
|
|
|
|
static int ss_delay = 50;
|
|
|
|
module_param(ss_delay, int, S_IRUGO | S_IWUSR);
|
|
|
|
MODULE_PARM_DESC(ss_delay,
|
|
|
|
"seconds to delay before entering selective suspend");
|
|
|
|
|
|
|
|
static int needs_remote_wakeup;
|
|
|
|
module_param(needs_remote_wakeup, int, S_IRUGO | S_IWUSR);
|
|
|
|
MODULE_PARM_DESC(needs_remote_wakeup, "ss state needs remote wakeup supported");
|
|
|
|
|
|
|
|
#ifdef SUPPORT_FILE_OP
|
|
|
|
static const struct file_operations rts51x_fops = {
|
|
|
|
.owner = THIS_MODULE,
|
|
|
|
.read = rts51x_read,
|
|
|
|
.write = rts51x_write,
|
|
|
|
.unlocked_ioctl = rts51x_ioctl,
|
|
|
|
.open = rts51x_open,
|
|
|
|
.release = rts51x_release,
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
* usb class driver info in order to get a minor number from the usb core,
|
|
|
|
* and to have the device registered with the driver core
|
|
|
|
*/
|
|
|
|
static struct usb_class_driver rts51x_class = {
|
|
|
|
.name = "rts51x%d",
|
|
|
|
.fops = &rts51x_fops,
|
|
|
|
.minor_base = 192,
|
|
|
|
};
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef CONFIG_PM /* Minimal support for suspend and resume */
|
|
|
|
|
|
|
|
static inline void usb_autopm_enable(struct usb_interface *intf)
|
|
|
|
{
|
|
|
|
atomic_set(&intf->pm_usage_cnt, 1);
|
|
|
|
usb_autopm_put_interface(intf);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void usb_autopm_disable(struct usb_interface *intf)
|
|
|
|
{
|
|
|
|
atomic_set(&intf->pm_usage_cnt, 0);
|
|
|
|
usb_autopm_get_interface(intf);
|
|
|
|
}
|
|
|
|
|
2012-05-04 18:02:46 +00:00
|
|
|
static void rts51x_try_to_enter_ss(struct rts51x_chip *chip)
|
2011-07-19 09:10:35 +00:00
|
|
|
{
|
|
|
|
RTS51X_DEBUGP("Ready to enter SS state\n");
|
|
|
|
usb_autopm_enable(chip->usb->pusb_intf);
|
|
|
|
}
|
|
|
|
|
|
|
|
void rts51x_try_to_exit_ss(struct rts51x_chip *chip)
|
|
|
|
{
|
|
|
|
RTS51X_DEBUGP("Exit from SS state\n");
|
|
|
|
usb_autopm_disable(chip->usb->pusb_intf);
|
|
|
|
}
|
|
|
|
|
|
|
|
int rts51x_suspend(struct usb_interface *iface, pm_message_t message)
|
|
|
|
{
|
|
|
|
struct rts51x_chip *chip = usb_get_intfdata(iface);
|
|
|
|
|
|
|
|
RTS51X_DEBUGP("%s, message.event = 0x%x\n", __func__, message.event);
|
|
|
|
|
|
|
|
/* Wait until no command is running */
|
|
|
|
mutex_lock(&chip->usb->dev_mutex);
|
|
|
|
|
|
|
|
chip->fake_card_ready = chip->card_ready;
|
|
|
|
rts51x_do_before_power_down(chip);
|
|
|
|
|
|
|
|
if (message.event == PM_EVENT_AUTO_SUSPEND) {
|
|
|
|
RTS51X_DEBUGP("Enter SS state");
|
|
|
|
chip->resume_from_scsi = 0;
|
|
|
|
RTS51X_SET_STAT(chip, STAT_SS);
|
|
|
|
} else {
|
|
|
|
RTS51X_DEBUGP("Enter SUSPEND state");
|
|
|
|
RTS51X_SET_STAT(chip, STAT_SUSPEND);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* When runtime PM is working, we'll set a flag to indicate
|
|
|
|
* whether we should autoresume when a SCSI request arrives. */
|
|
|
|
|
|
|
|
mutex_unlock(&chip->usb->dev_mutex);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int rts51x_resume(struct usb_interface *iface)
|
|
|
|
{
|
|
|
|
struct rts51x_chip *chip = usb_get_intfdata(iface);
|
|
|
|
|
|
|
|
RTS51X_DEBUGP("%s\n", __func__);
|
|
|
|
|
|
|
|
if (!RTS51X_CHK_STAT(chip, STAT_SS) || !chip->resume_from_scsi) {
|
|
|
|
mutex_lock(&chip->usb->dev_mutex);
|
|
|
|
|
|
|
|
if (chip->option.ss_en) {
|
|
|
|
if (GET_PM_USAGE_CNT(chip) <= 0) {
|
|
|
|
/* Remote wake up, increase pm_usage_cnt */
|
|
|
|
RTS51X_DEBUGP("Incr pm_usage_cnt\n");
|
|
|
|
SET_PM_USAGE_CNT(chip, 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
RTS51X_SET_STAT(chip, STAT_RUN);
|
|
|
|
|
|
|
|
rts51x_init_chip(chip);
|
|
|
|
rts51x_init_cards(chip);
|
|
|
|
|
|
|
|
mutex_unlock(&chip->usb->dev_mutex);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int rts51x_reset_resume(struct usb_interface *iface)
|
|
|
|
{
|
|
|
|
struct rts51x_chip *chip = usb_get_intfdata(iface);
|
|
|
|
|
|
|
|
RTS51X_DEBUGP("%s\n", __func__);
|
|
|
|
|
|
|
|
mutex_lock(&chip->usb->dev_mutex);
|
|
|
|
|
|
|
|
RTS51X_SET_STAT(chip, STAT_RUN);
|
|
|
|
|
|
|
|
if (chip->option.ss_en)
|
|
|
|
SET_PM_USAGE_CNT(chip, 1);
|
|
|
|
|
|
|
|
rts51x_init_chip(chip);
|
|
|
|
rts51x_init_cards(chip);
|
|
|
|
|
|
|
|
mutex_unlock(&chip->usb->dev_mutex);
|
|
|
|
|
|
|
|
/* FIXME: Notify the subdrivers that they need to reinitialize
|
|
|
|
* the device */
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
#else /* CONFIG_PM */
|
|
|
|
|
2012-05-04 18:02:46 +00:00
|
|
|
static void rts51x_try_to_enter_ss(struct rts51x_chip *chip)
|
2011-07-19 09:10:35 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void rts51x_try_to_exit_ss(struct rts51x_chip *chip)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif /* CONFIG_PM */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The next two routines get called just before and just after
|
|
|
|
* a USB port reset, whether from this driver or a different one.
|
|
|
|
*/
|
|
|
|
|
|
|
|
int rts51x_pre_reset(struct usb_interface *iface)
|
|
|
|
{
|
|
|
|
struct rts51x_chip *chip = usb_get_intfdata(iface);
|
|
|
|
|
|
|
|
RTS51X_DEBUGP("%s\n", __func__);
|
|
|
|
|
|
|
|
/* Make sure no command runs during the reset */
|
|
|
|
mutex_lock(&chip->usb->dev_mutex);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int rts51x_post_reset(struct usb_interface *iface)
|
|
|
|
{
|
|
|
|
struct rts51x_chip *chip = usb_get_intfdata(iface);
|
|
|
|
|
|
|
|
RTS51X_DEBUGP("%s\n", __func__);
|
|
|
|
|
|
|
|
/* Report the reset to the SCSI core */
|
|
|
|
/* usb_stor_report_bus_reset(us); */
|
|
|
|
|
|
|
|
/* FIXME: Notify the subdrivers that they need to reinitialize
|
|
|
|
* the device */
|
|
|
|
|
|
|
|
mutex_unlock(&chip->usb->dev_mutex);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int rts51x_control_thread(void *__chip)
|
|
|
|
{
|
|
|
|
struct rts51x_chip *chip = (struct rts51x_chip *)__chip;
|
|
|
|
struct Scsi_Host *host = rts51x_to_host(chip);
|
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
if (wait_for_completion_interruptible(&chip->usb->cmnd_ready))
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (test_bit(FLIDX_DISCONNECTING, &chip->usb->dflags)) {
|
|
|
|
RTS51X_DEBUGP("-- exiting from rts51x-control\n");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* lock the device pointers */
|
|
|
|
mutex_lock(&(chip->usb->dev_mutex));
|
|
|
|
|
|
|
|
/* lock access to the state */
|
|
|
|
scsi_lock(host);
|
|
|
|
|
|
|
|
/* When we are called with no command pending, we're done */
|
|
|
|
if (chip->srb == NULL) {
|
|
|
|
scsi_unlock(host);
|
|
|
|
mutex_unlock(&chip->usb->dev_mutex);
|
|
|
|
RTS51X_DEBUGP("-- exiting from control thread\n");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* has the command timed out *already* ? */
|
|
|
|
if (test_bit(FLIDX_TIMED_OUT, &chip->usb->dflags)) {
|
|
|
|
chip->srb->result = DID_ABORT << 16;
|
|
|
|
goto SkipForAbort;
|
|
|
|
}
|
|
|
|
|
|
|
|
scsi_unlock(host);
|
|
|
|
|
|
|
|
/* reject the command if the direction indicator
|
|
|
|
* is UNKNOWN
|
|
|
|
*/
|
|
|
|
if (chip->srb->sc_data_direction == DMA_BIDIRECTIONAL) {
|
|
|
|
RTS51X_DEBUGP("UNKNOWN data direction\n");
|
|
|
|
chip->srb->result = DID_ERROR << 16;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* reject if target != 0 or if LUN is higher than
|
|
|
|
* the maximum known LUN
|
|
|
|
*/
|
|
|
|
else if (chip->srb->device->id) {
|
|
|
|
RTS51X_DEBUGP("Bad target number (%d:%d)\n",
|
|
|
|
chip->srb->device->id,
|
|
|
|
chip->srb->device->lun);
|
|
|
|
chip->srb->result = DID_BAD_TARGET << 16;
|
|
|
|
}
|
|
|
|
|
|
|
|
else if (chip->srb->device->lun > chip->max_lun) {
|
|
|
|
RTS51X_DEBUGP("Bad LUN (%d:%d)\n",
|
|
|
|
chip->srb->device->id,
|
|
|
|
chip->srb->device->lun);
|
|
|
|
chip->srb->result = DID_BAD_TARGET << 16;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* we've got a command, let's do it! */
|
|
|
|
else {
|
|
|
|
RTS51X_DEBUG(scsi_show_command(chip->srb));
|
|
|
|
rts51x_invoke_transport(chip->srb, chip);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* lock access to the state */
|
|
|
|
scsi_lock(host);
|
|
|
|
|
|
|
|
/* indicate that the command is done */
|
|
|
|
if (chip->srb->result != DID_ABORT << 16)
|
|
|
|
chip->srb->scsi_done(chip->srb);
|
|
|
|
else
|
|
|
|
SkipForAbort :
|
|
|
|
RTS51X_DEBUGP("scsi command aborted\n");
|
|
|
|
|
|
|
|
/* If an abort request was received we need to signal that
|
|
|
|
* the abort has finished. The proper test for this is
|
|
|
|
* the TIMED_OUT flag, not srb->result == DID_ABORT, because
|
|
|
|
* the timeout might have occurred after the command had
|
|
|
|
* already completed with a different result code. */
|
|
|
|
if (test_bit(FLIDX_TIMED_OUT, &chip->usb->dflags)) {
|
|
|
|
complete(&(chip->usb->notify));
|
|
|
|
|
|
|
|
/* Allow USB transfers to resume */
|
|
|
|
clear_bit(FLIDX_ABORTING, &chip->usb->dflags);
|
|
|
|
clear_bit(FLIDX_TIMED_OUT, &chip->usb->dflags);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* finished working on this command */
|
|
|
|
chip->srb = NULL;
|
|
|
|
scsi_unlock(host);
|
|
|
|
|
|
|
|
/* unlock the device pointers */
|
|
|
|
mutex_unlock(&chip->usb->dev_mutex);
|
|
|
|
} /* for (;;) */
|
|
|
|
|
|
|
|
complete(&chip->usb->control_exit);
|
|
|
|
|
|
|
|
/* Wait until we are told to stop */
|
|
|
|
/* for (;;) {
|
|
|
|
set_current_state(TASK_INTERRUPTIBLE);
|
|
|
|
if (kthread_should_stop())
|
|
|
|
break;
|
|
|
|
schedule();
|
|
|
|
}
|
|
|
|
__set_current_state(TASK_RUNNING);*/
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int rts51x_polling_thread(void *__chip)
|
|
|
|
{
|
|
|
|
struct rts51x_chip *chip = (struct rts51x_chip *)__chip;
|
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
wait_timeout(POLLING_INTERVAL);
|
|
|
|
|
|
|
|
/* if the device has disconnected, we are free to exit */
|
|
|
|
if (test_bit(FLIDX_DISCONNECTING, &chip->usb->dflags)) {
|
|
|
|
RTS51X_DEBUGP("-- exiting from rts51x-polling\n");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* if the device has disconnected, we are free to exit */
|
|
|
|
/* if (kthread_should_stop()) {
|
|
|
|
printk(KERN_INFO "Stop polling thread!\n");
|
|
|
|
break;
|
|
|
|
} */
|
|
|
|
|
|
|
|
#ifdef CONFIG_PM
|
|
|
|
if (RTS51X_CHK_STAT(chip, STAT_SS) ||
|
|
|
|
RTS51X_CHK_STAT(chip, STAT_SS_PRE) ||
|
|
|
|
RTS51X_CHK_STAT(chip, STAT_SUSPEND)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ss_en) {
|
|
|
|
if (RTS51X_CHK_STAT(chip, STAT_IDLE)) {
|
|
|
|
if (chip->ss_counter <
|
|
|
|
(ss_delay * 1000 / POLLING_INTERVAL)) {
|
|
|
|
chip->ss_counter++;
|
|
|
|
} else {
|
|
|
|
/* Prepare SS state */
|
|
|
|
RTS51X_SET_STAT(chip, STAT_SS_PRE);
|
|
|
|
rts51x_try_to_enter_ss(chip);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
chip->ss_counter = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
mspro_polling_format_status(chip);
|
|
|
|
|
|
|
|
/* lock the device pointers */
|
|
|
|
mutex_lock(&(chip->usb->dev_mutex));
|
|
|
|
|
|
|
|
rts51x_polling_func(chip);
|
|
|
|
|
|
|
|
/* unlock the device pointers */
|
|
|
|
mutex_unlock(&chip->usb->dev_mutex);
|
|
|
|
} /* for (;;) */
|
|
|
|
|
|
|
|
complete(&chip->usb->polling_exit);
|
|
|
|
|
|
|
|
/* Wait until we are told to stop */
|
|
|
|
/* for (;;) {
|
|
|
|
set_current_state(TASK_INTERRUPTIBLE);
|
|
|
|
if (kthread_should_stop())
|
|
|
|
break;
|
|
|
|
schedule();
|
|
|
|
}
|
|
|
|
__set_current_state(TASK_RUNNING); */
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Associate our private data with the USB device */
|
|
|
|
static int associate_dev(struct rts51x_chip *chip, struct usb_interface *intf)
|
|
|
|
{
|
|
|
|
struct rts51x_usb *rts51x = chip->usb;
|
|
|
|
#ifdef SUPPORT_FILE_OP
|
|
|
|
int retval;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* Fill in the device-related fields */
|
|
|
|
rts51x->pusb_dev = interface_to_usbdev(intf);
|
|
|
|
rts51x->pusb_intf = intf;
|
|
|
|
rts51x->ifnum = intf->cur_altsetting->desc.bInterfaceNumber;
|
|
|
|
RTS51X_DEBUGP("Vendor: 0x%04x, Product: 0x%04x, Revision: 0x%04x\n",
|
|
|
|
le16_to_cpu(rts51x->pusb_dev->descriptor.idVendor),
|
|
|
|
le16_to_cpu(rts51x->pusb_dev->descriptor.idProduct),
|
|
|
|
le16_to_cpu(rts51x->pusb_dev->descriptor.bcdDevice));
|
|
|
|
RTS51X_DEBUGP("Interface Subclass: 0x%02x, Protocol: 0x%02x\n",
|
|
|
|
intf->cur_altsetting->desc.bInterfaceSubClass,
|
|
|
|
intf->cur_altsetting->desc.bInterfaceProtocol);
|
|
|
|
|
|
|
|
/* Store our private data in the interface */
|
|
|
|
usb_set_intfdata(intf, chip);
|
|
|
|
|
|
|
|
#ifdef SUPPORT_FILE_OP
|
|
|
|
/* we can register the device now, as it is ready */
|
|
|
|
retval = usb_register_dev(intf, &rts51x_class);
|
|
|
|
if (retval) {
|
|
|
|
/* something prevented us from registering this driver */
|
|
|
|
RTS51X_DEBUGP("Not able to get a minor for this device.");
|
|
|
|
usb_set_intfdata(intf, NULL);
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* Allocate the device-related DMA-mapped buffers */
|
|
|
|
rts51x->cr = usb_buffer_alloc(rts51x->pusb_dev, sizeof(*rts51x->cr),
|
|
|
|
GFP_KERNEL, &rts51x->cr_dma);
|
|
|
|
if (!rts51x->cr) {
|
|
|
|
RTS51X_DEBUGP("usb_ctrlrequest allocation failed\n");
|
|
|
|
usb_set_intfdata(intf, NULL);
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
rts51x->iobuf = usb_buffer_alloc(rts51x->pusb_dev, RTS51X_IOBUF_SIZE,
|
|
|
|
GFP_KERNEL, &rts51x->iobuf_dma);
|
|
|
|
if (!rts51x->iobuf) {
|
|
|
|
RTS51X_DEBUGP("I/O buffer allocation failed\n");
|
|
|
|
usb_set_intfdata(intf, NULL);
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void rts51x_init_options(struct rts51x_chip *chip)
|
|
|
|
{
|
|
|
|
struct rts51x_option *option = &(chip->option);
|
|
|
|
|
|
|
|
option->mspro_formatter_enable = 1;
|
|
|
|
|
|
|
|
option->fpga_sd_sdr104_clk = CLK_100;
|
|
|
|
option->fpga_sd_sdr50_clk = CLK_100;
|
|
|
|
option->fpga_sd_ddr50_clk = CLK_100;
|
|
|
|
option->fpga_sd_hs_clk = CLK_100;
|
|
|
|
option->fpga_mmc_52m_clk = CLK_80;
|
|
|
|
option->fpga_ms_hg_clk = CLK_80;
|
|
|
|
option->fpga_ms_4bit_clk = CLK_80;
|
|
|
|
|
|
|
|
option->asic_sd_sdr104_clk = 98;
|
|
|
|
option->asic_sd_sdr50_clk = 98;
|
|
|
|
option->asic_sd_ddr50_clk = 98;
|
|
|
|
option->asic_sd_hs_clk = 97;
|
|
|
|
option->asic_mmc_52m_clk = 95;
|
|
|
|
option->asic_ms_hg_clk = 116;
|
|
|
|
option->asic_ms_4bit_clk = 77;
|
|
|
|
|
|
|
|
option->sd_ddr_tx_phase = 0;
|
|
|
|
option->mmc_ddr_tx_phase = 1;
|
|
|
|
|
|
|
|
option->sd_speed_prior = 0;
|
|
|
|
option->sd_ctl =
|
|
|
|
SD_PUSH_POINT_AUTO | SD_SAMPLE_POINT_AUTO | SUPPORT_UHS50_MMC44;
|
|
|
|
|
|
|
|
option->ss_en = ss_en;
|
|
|
|
option->ss_delay = ss_delay;
|
|
|
|
|
|
|
|
option->auto_delink_en = auto_delink_en;
|
|
|
|
|
|
|
|
option->FT2_fast_mode = 0;
|
|
|
|
option->pwr_delay = 800;
|
|
|
|
option->xd_rw_step = 0;
|
|
|
|
option->D3318_off_delay = 50;
|
|
|
|
option->delink_delay = 100;
|
|
|
|
option->rts5129_D3318_off_enable = 0;
|
|
|
|
option->sd20_pad_drive = 0;
|
|
|
|
option->reset_or_rw_fail_set_pad_drive = 1;
|
|
|
|
option->debounce_num = 2;
|
|
|
|
option->led_toggle_interval = 6;
|
|
|
|
option->xd_rwn_step = 0;
|
|
|
|
option->sd_send_status_en = 0;
|
|
|
|
option->sdr50_tx_phase = 0x01;
|
|
|
|
option->sdr50_rx_phase = 0x05;
|
|
|
|
option->ddr50_tx_phase = 0x09;
|
|
|
|
option->ddr50_rx_phase = 0x06;
|
|
|
|
option->sdr50_phase_sel = 0;
|
|
|
|
option->sd30_pad_drive = 1;
|
|
|
|
option->ms_errreg_fix = 0;
|
|
|
|
option->reset_mmc_first = 0;
|
|
|
|
option->speed_mmc = 1;
|
|
|
|
option->led_always_on = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Get the pipe settings */
|
|
|
|
static int get_pipes(struct rts51x_chip *chip)
|
|
|
|
{
|
|
|
|
struct rts51x_usb *rts51x = chip->usb;
|
|
|
|
struct usb_host_interface *altsetting =
|
|
|
|
rts51x->pusb_intf->cur_altsetting;
|
|
|
|
int i;
|
|
|
|
struct usb_endpoint_descriptor *ep;
|
|
|
|
struct usb_endpoint_descriptor *ep_in = NULL;
|
|
|
|
struct usb_endpoint_descriptor *ep_out = NULL;
|
|
|
|
struct usb_endpoint_descriptor *ep_int = NULL;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Find the first endpoint of each type we need.
|
|
|
|
* We are expecting a minimum of 2 endpoints - in and out (bulk).
|
|
|
|
* An optional interrupt-in is OK (necessary for CBI protocol).
|
|
|
|
* We will ignore any others.
|
|
|
|
*/
|
|
|
|
for (i = 0; i < altsetting->desc.bNumEndpoints; i++) {
|
|
|
|
ep = &altsetting->endpoint[i].desc;
|
|
|
|
|
|
|
|
if (usb_endpoint_xfer_bulk(ep)) {
|
|
|
|
if (usb_endpoint_dir_in(ep)) {
|
|
|
|
if (!ep_in)
|
|
|
|
ep_in = ep;
|
|
|
|
} else {
|
|
|
|
if (!ep_out)
|
|
|
|
ep_out = ep;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
else if (usb_endpoint_is_int_in(ep)) {
|
|
|
|
if (!ep_int)
|
|
|
|
ep_int = ep;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!ep_in || !ep_out) {
|
|
|
|
RTS51X_DEBUGP("Endpoint sanity check failed!"
|
|
|
|
"Rejecting dev.\n");
|
|
|
|
return -EIO;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Calculate and store the pipe values */
|
|
|
|
rts51x->send_ctrl_pipe = usb_sndctrlpipe(rts51x->pusb_dev, 0);
|
|
|
|
rts51x->recv_ctrl_pipe = usb_rcvctrlpipe(rts51x->pusb_dev, 0);
|
|
|
|
rts51x->send_bulk_pipe = usb_sndbulkpipe(rts51x->pusb_dev,
|
|
|
|
usb_endpoint_num(ep_out));
|
|
|
|
rts51x->recv_bulk_pipe = usb_rcvbulkpipe(rts51x->pusb_dev,
|
|
|
|
usb_endpoint_num(ep_in));
|
|
|
|
if (ep_int) {
|
|
|
|
rts51x->recv_intr_pipe = usb_rcvintpipe(rts51x->pusb_dev,
|
|
|
|
usb_endpoint_num
|
|
|
|
(ep_int));
|
|
|
|
rts51x->ep_bInterval = ep_int->bInterval;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Initialize all the dynamic resources we need */
|
|
|
|
static int rts51x_acquire_resources(struct rts51x_chip *chip)
|
|
|
|
{
|
|
|
|
struct rts51x_usb *rts51x = chip->usb;
|
|
|
|
int retval;
|
|
|
|
|
|
|
|
rts51x->current_urb = usb_alloc_urb(0, GFP_KERNEL);
|
|
|
|
if (!rts51x->current_urb) {
|
|
|
|
RTS51X_DEBUGP("URB allocation failed\n");
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
rts51x->intr_urb = usb_alloc_urb(0, GFP_KERNEL);
|
|
|
|
if (!rts51x->intr_urb) {
|
|
|
|
RTS51X_DEBUGP("URB allocation failed\n");
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
chip->cmd_buf = chip->rsp_buf = rts51x->iobuf;
|
|
|
|
|
|
|
|
rts51x_init_options(chip);
|
|
|
|
|
|
|
|
/* Init rts51xx device */
|
|
|
|
retval = rts51x_init_chip(chip);
|
|
|
|
if (retval != STATUS_SUCCESS)
|
|
|
|
return -EIO;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Release all our dynamic resources */
|
|
|
|
static void rts51x_release_resources(struct rts51x_chip *chip)
|
|
|
|
{
|
|
|
|
RTS51X_DEBUGP("-- %s\n", __func__);
|
|
|
|
|
|
|
|
/* Tell the control thread to exit. The SCSI host must
|
|
|
|
* already have been removed and the DISCONNECTING flag set
|
|
|
|
* so that we won't accept any more commands.
|
|
|
|
*/
|
|
|
|
RTS51X_DEBUGP("-- sending exit command to thread\n");
|
|
|
|
complete(&chip->usb->cmnd_ready);
|
|
|
|
if (chip->usb->ctl_thread)
|
|
|
|
wait_for_completion(&chip->usb->control_exit);
|
|
|
|
/* kthread_stop(chip->usb->ctl_thread); */
|
|
|
|
if (chip->usb->polling_thread)
|
|
|
|
wait_for_completion(&chip->usb->polling_exit);
|
|
|
|
|
|
|
|
/* if (chip->usb->polling_thread)
|
|
|
|
kthread_stop(chip->usb->polling_thread); */
|
|
|
|
|
|
|
|
wait_timeout(200);
|
|
|
|
|
|
|
|
/* Release rts51xx device here */
|
|
|
|
rts51x_release_chip(chip);
|
|
|
|
|
|
|
|
usb_free_urb(chip->usb->current_urb);
|
|
|
|
usb_free_urb(chip->usb->intr_urb);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Dissociate from the USB device */
|
|
|
|
static void dissociate_dev(struct rts51x_chip *chip)
|
|
|
|
{
|
|
|
|
struct rts51x_usb *rts51x = chip->usb;
|
|
|
|
|
|
|
|
RTS51X_DEBUGP("-- %s\n", __func__);
|
|
|
|
|
|
|
|
/* Free the device-related DMA-mapped buffers */
|
|
|
|
if (rts51x->cr)
|
|
|
|
usb_buffer_free(rts51x->pusb_dev, sizeof(*rts51x->cr),
|
|
|
|
rts51x->cr, rts51x->cr_dma);
|
|
|
|
if (rts51x->iobuf)
|
|
|
|
usb_buffer_free(rts51x->pusb_dev, RTS51X_IOBUF_SIZE,
|
|
|
|
rts51x->iobuf, rts51x->iobuf_dma);
|
|
|
|
|
|
|
|
/* Remove our private data from the interface */
|
|
|
|
usb_set_intfdata(rts51x->pusb_intf, NULL);
|
|
|
|
|
|
|
|
#ifdef SUPPORT_FILE_OP
|
|
|
|
/* give back our minor */
|
|
|
|
usb_deregister_dev(rts51x->pusb_intf, &rts51x_class);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
kfree(rts51x);
|
|
|
|
chip->usb = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* First stage of disconnect processing: stop SCSI scanning,
|
|
|
|
* remove the host, and stop accepting new commands
|
|
|
|
*/
|
|
|
|
static void quiesce_and_remove_host(struct rts51x_chip *chip)
|
|
|
|
{
|
|
|
|
struct rts51x_usb *rts51x = chip->usb;
|
|
|
|
struct Scsi_Host *host = rts51x_to_host(chip);
|
|
|
|
|
|
|
|
/* If the device is really gone, cut short reset delays */
|
|
|
|
if (rts51x->pusb_dev->state == USB_STATE_NOTATTACHED)
|
|
|
|
set_bit(FLIDX_DISCONNECTING, &rts51x->dflags);
|
|
|
|
|
|
|
|
/* Removing the host will perform an orderly shutdown: caches
|
|
|
|
* synchronized, disks spun down, etc.
|
|
|
|
*/
|
|
|
|
scsi_remove_host(host);
|
|
|
|
|
|
|
|
/* Prevent any new commands from being accepted and cut short
|
|
|
|
* reset delays.
|
|
|
|
*/
|
|
|
|
scsi_lock(host);
|
|
|
|
set_bit(FLIDX_DISCONNECTING, &rts51x->dflags);
|
|
|
|
scsi_unlock(host);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Second stage of disconnect processing: deallocate all resources */
|
|
|
|
static void release_everything(struct rts51x_chip *chip)
|
|
|
|
{
|
|
|
|
rts51x_release_resources(chip);
|
|
|
|
dissociate_dev(chip);
|
|
|
|
|
|
|
|
/* Drop our reference to the host; the SCSI core will free it
|
|
|
|
* (and "chip" along with it) when the refcount becomes 0. */
|
|
|
|
scsi_host_put(rts51x_to_host(chip));
|
|
|
|
}
|
|
|
|
|
|
|
|
static int rts51x_probe(struct usb_interface *intf,
|
|
|
|
const struct usb_device_id *id)
|
|
|
|
{
|
|
|
|
struct Scsi_Host *host;
|
|
|
|
struct rts51x_chip *chip;
|
|
|
|
struct rts51x_usb *rts51x;
|
|
|
|
int result;
|
|
|
|
struct task_struct *th;
|
|
|
|
|
|
|
|
RTS51X_DEBUGP("%s detected\n", RTS51X_NAME);
|
|
|
|
|
|
|
|
rts51x = kzalloc(sizeof(struct rts51x_usb), GFP_KERNEL);
|
|
|
|
if (!rts51x) {
|
|
|
|
printk(KERN_WARNING RTS51X_TIP
|
|
|
|
"Unable to allocate rts51x_usb\n");
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Ask the SCSI layer to allocate a host structure, with extra
|
|
|
|
* space at the end for our private us_data structure.
|
|
|
|
*/
|
|
|
|
host = scsi_host_alloc(&rts51x_host_template, sizeof(*chip));
|
|
|
|
if (!host) {
|
|
|
|
printk(KERN_WARNING RTS51X_TIP
|
|
|
|
"Unable to allocate the scsi host\n");
|
|
|
|
kfree(rts51x);
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Allow 16-byte CDBs and thus > 2TB
|
|
|
|
*/
|
|
|
|
host->max_cmd_len = 16;
|
|
|
|
chip = host_to_rts51x(host);
|
|
|
|
memset(chip, 0, sizeof(struct rts51x_chip));
|
|
|
|
|
|
|
|
chip->vendor_id = id->idVendor;
|
|
|
|
chip->product_id = id->idProduct;
|
|
|
|
|
|
|
|
mutex_init(&(rts51x->dev_mutex));
|
|
|
|
init_completion(&rts51x->cmnd_ready);
|
|
|
|
init_completion(&rts51x->control_exit);
|
|
|
|
init_completion(&rts51x->polling_exit);
|
|
|
|
init_completion(&(rts51x->notify));
|
|
|
|
|
|
|
|
chip->usb = rts51x;
|
|
|
|
|
|
|
|
/* Associate the us_data structure with the USB device */
|
|
|
|
result = associate_dev(chip, intf);
|
|
|
|
if (result)
|
|
|
|
goto BadDevice;
|
|
|
|
|
|
|
|
/* Find the endpoints and calculate pipe values */
|
|
|
|
result = get_pipes(chip);
|
|
|
|
if (result)
|
|
|
|
goto BadDevice;
|
|
|
|
|
|
|
|
/* Acquire all the other resources and add the host */
|
|
|
|
result = rts51x_acquire_resources(chip);
|
|
|
|
if (result)
|
|
|
|
goto BadDevice;
|
|
|
|
|
|
|
|
/* Start up our control thread */
|
|
|
|
th = kthread_run(rts51x_control_thread, chip, RTS51X_CTL_THREAD);
|
|
|
|
if (IS_ERR(th)) {
|
|
|
|
printk(KERN_WARNING RTS51X_TIP
|
|
|
|
"Unable to start control thread\n");
|
|
|
|
result = PTR_ERR(th);
|
|
|
|
goto BadDevice;
|
|
|
|
}
|
|
|
|
rts51x->ctl_thread = th;
|
|
|
|
|
|
|
|
result = scsi_add_host(rts51x_to_host(chip), &rts51x->pusb_intf->dev);
|
|
|
|
if (result) {
|
|
|
|
printk(KERN_WARNING RTS51X_TIP "Unable to add the scsi host\n");
|
|
|
|
goto BadDevice;
|
|
|
|
}
|
|
|
|
scsi_scan_host(rts51x_to_host(chip));
|
|
|
|
|
|
|
|
/* Start up our polling thread */
|
|
|
|
th = kthread_run(rts51x_polling_thread, chip, RTS51X_POLLING_THREAD);
|
|
|
|
if (IS_ERR(th)) {
|
|
|
|
printk(KERN_WARNING RTS51X_TIP
|
|
|
|
"Unable to start polling thread\n");
|
|
|
|
result = PTR_ERR(th);
|
|
|
|
goto BadDevice;
|
|
|
|
}
|
|
|
|
rts51x->polling_thread = th;
|
|
|
|
|
|
|
|
#ifdef CONFIG_PM
|
|
|
|
if (ss_en) {
|
|
|
|
rts51x->pusb_intf->needs_remote_wakeup = needs_remote_wakeup;
|
|
|
|
SET_PM_USAGE_CNT(chip, 1);
|
|
|
|
RTS51X_DEBUGP("pm_usage_cnt = %d\n", GET_PM_USAGE_CNT(chip));
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* We come here if there are any problems */
|
|
|
|
BadDevice:
|
|
|
|
RTS51X_DEBUGP("rts51x_probe() failed\n");
|
|
|
|
release_everything(chip);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void rts51x_disconnect(struct usb_interface *intf)
|
|
|
|
{
|
|
|
|
struct rts51x_chip *chip = (struct rts51x_chip *)usb_get_intfdata(intf);
|
|
|
|
|
|
|
|
RTS51X_DEBUGP("rts51x_disconnect() called\n");
|
|
|
|
quiesce_and_remove_host(chip);
|
|
|
|
release_everything(chip);
|
|
|
|
}
|
|
|
|
|
|
|
|
/***********************************************************************
|
|
|
|
* Initialization and registration
|
|
|
|
***********************************************************************/
|
|
|
|
|
|
|
|
struct usb_device_id rts5139_usb_ids[] = {
|
|
|
|
{USB_DEVICE(0x0BDA, 0x0139)},
|
|
|
|
{USB_DEVICE(0x0BDA, 0x0129)},
|
|
|
|
{} /* Terminating entry */
|
|
|
|
};
|
|
|
|
EXPORT_SYMBOL_GPL(rts5139_usb_ids);
|
|
|
|
|
|
|
|
MODULE_DEVICE_TABLE(usb, rts5139_usb_ids);
|
|
|
|
|
|
|
|
struct usb_driver rts51x_driver = {
|
|
|
|
.name = RTS51X_NAME,
|
|
|
|
.probe = rts51x_probe,
|
|
|
|
.disconnect = rts51x_disconnect,
|
|
|
|
.suspend = rts51x_suspend,
|
|
|
|
.resume = rts51x_resume,
|
|
|
|
.reset_resume = rts51x_reset_resume,
|
|
|
|
.pre_reset = rts51x_pre_reset,
|
|
|
|
.post_reset = rts51x_post_reset,
|
|
|
|
.id_table = rts5139_usb_ids,
|
|
|
|
.soft_unbind = 1,
|
|
|
|
};
|
|
|
|
|
2011-11-18 17:42:11 +00:00
|
|
|
module_usb_driver(rts51x_driver);
|