/* Driver for Realtek PCI-Express card reader
*
* Copyright(c) 2009-2013 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:
* Wei WANG <wei_wang@realsil.com.cn>
*/
#include <linux/pci.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/dma-mapping.h>
#include <linux/highmem.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/idr.h>
#include <linux/platform_device.h>
#include <linux/mfd/core.h>
#include <linux/rtsx_pci.h>
#include <linux/mmc/card.h>
#include <asm/unaligned.h>
#include "rtsx_pcr.h"
static bool msi_en = true;
module_param(msi_en, bool, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(msi_en, "Enable MSI");
static DEFINE_IDR(rtsx_pci_idr);
static DEFINE_SPINLOCK(rtsx_pci_lock);
static struct mfd_cell rtsx_pcr_cells[] = {
[RTSX_SD_CARD] = {
.name = DRV_NAME_RTSX_PCI_SDMMC,
},
[RTSX_MS_CARD] = {
.name = DRV_NAME_RTSX_PCI_MS,
},
};
static const struct pci_device_id rtsx_pci_ids[] = {
{ PCI_DEVICE(0x10EC, 0x5209), PCI_CLASS_OTHERS << 16, 0xFF0000 },
{ PCI_DEVICE(0x10EC, 0x5229), PCI_CLASS_OTHERS << 16, 0xFF0000 },
{ PCI_DEVICE(0x10EC, 0x5289), PCI_CLASS_OTHERS << 16, 0xFF0000 },
{ PCI_DEVICE(0x10EC, 0x5227), PCI_CLASS_OTHERS << 16, 0xFF0000 },
{ PCI_DEVICE(0x10EC, 0x522A), PCI_CLASS_OTHERS << 16, 0xFF0000 },
{ PCI_DEVICE(0x10EC, 0x5249), PCI_CLASS_OTHERS << 16, 0xFF0000 },
{ PCI_DEVICE(0x10EC, 0x5287), PCI_CLASS_OTHERS << 16, 0xFF0000 },
{ PCI_DEVICE(0x10EC, 0x5286), PCI_CLASS_OTHERS << 16, 0xFF0000 },
{ PCI_DEVICE(0x10EC, 0x524A), PCI_CLASS_OTHERS << 16, 0xFF0000 },
{ PCI_DEVICE(0x10EC, 0x525A), PCI_CLASS_OTHERS << 16, 0xFF0000 },
{ 0, }
};
MODULE_DEVICE_TABLE(pci, rtsx_pci_ids);
static inline void rtsx_pci_enable_aspm(struct rtsx_pcr *pcr)
{
rtsx_pci_update_cfg_byte(pcr, pcr->pcie_cap + PCI_EXP_LNKCTL,
0xFC, pcr->aspm_en);
}
static inline void rtsx_pci_disable_aspm(struct rtsx_pcr *pcr)
{
rtsx_pci_update_cfg_byte(pcr, pcr->pcie_cap + PCI_EXP_LNKCTL,
0xFC, 0);
}
int rtsx_comm_set_ltr_latency(struct rtsx_pcr *pcr, u32 latency)
{
rtsx_pci_write_register(pcr, MSGTXDATA0,
MASK_8_BIT_DEF, (u8) (latency & 0xFF));
rtsx_pci_write_register(pcr, MSGTXDATA1,
MASK_8_BIT_DEF, (u8)((latency >> 8) & 0xFF));
rtsx_pci_write_register(pcr, MSGTXDATA2,
MASK_8_BIT_DEF, (u8)((latency >> 16) & 0xFF));
rtsx_pci_write_register(pcr, MSGTXDATA3,
MASK_8_BIT_DEF, (u8)((latency >> 24) & 0xFF));
rtsx_pci_write_register(pcr, LTR_CTL, LTR_TX_EN_MASK |
LTR_LATENCY_MODE_MASK, LTR_TX_EN_1 | LTR_LATENCY_MODE_SW);
return 0;
}
int rtsx_set_ltr_latency(struct rtsx_pcr *pcr, u32 latency)
{
if (pcr->ops->set_ltr_latency)
return pcr->ops->set_ltr_latency(pcr, latency);
else
return rtsx_comm_set_ltr_latency(pcr, latency);
}
static void rtsx_comm_set_aspm(struct rtsx_pcr *pcr, bool enable)
{
struct rtsx_cr_option *option = &pcr->option;
if (pcr->aspm_enabled == enable)
return;
if (option->dev_aspm_mode == DEV_ASPM_DYNAMIC) {
if (enable)
rtsx_pci_enable_aspm(pcr);
else
rtsx_pci_disable_aspm(pcr);
} else if (option->dev_aspm_mode == DEV_ASPM_BACKDOOR) {
u8 mask = FORCE_ASPM_VAL_MASK;
u8 val = 0;
if (enable)
val = pcr->aspm_en;
rtsx_pci_write_register(pcr, ASPM_FORCE_CTL, mask, val);
}
pcr->aspm_enabled = enable;
}
static void rtsx_disable_aspm(struct rtsx_pcr *pcr)
{
if (pcr->ops->set_aspm)
pcr->ops->set_aspm(pcr, false);
else
rtsx_comm_set_aspm(pcr, false);
}
int rtsx_set_l1off_sub(struct rtsx_pcr *pcr, u8 val)
{
rtsx_pci_write_register(pcr, L1SUB_CONFIG3, 0xFF, val);
return 0;
}
void rtsx_set_l1off_sub_cfg_d0(struct rtsx_pcr *pcr, int active)
{
if (pcr->ops->set_l1off_cfg_sub_d0)
pcr->ops->set_l1off_cfg_sub_d0(pcr, active);
}
static void rtsx_comm_pm_full_on(struct rtsx_pcr *pcr)
{
struct rtsx_cr_option *option = &pcr->option;
rtsx_disable_aspm(pcr);
if (option->ltr_enabled)
rtsx_set_ltr_latency(pcr, option->ltr_active_latency);
if (rtsx_check_dev_flag(pcr, LTR_L1SS_PWR_GATE_EN))
rtsx_set_l1off_sub_cfg_d0(pcr, 1);
}
void rtsx_pm_full_on(struct rtsx_pcr *pcr)
{
if (pcr->ops->full_on)
pcr->ops->full_on(pcr);
else
rtsx_comm_pm_full_on(pcr);
}
void rtsx_pci_start_run(struct rtsx_pcr *pcr)
{
/* If pci device removed, don't queue idle work any more */
if (pcr->remove_pci)
return;
if (pcr->state != PDEV_STAT_RUN) {
pcr->state = PDEV_STAT_RUN;
if (pcr->ops->enable_auto_blink)
pcr->ops->enable_auto_blink(pcr);
rtsx_pm_full_on(pcr);
}
mod_delayed_work(system_wq, &pcr->idle_work, msecs_to_jiffies(200));
}
EXPORT_SYMBOL_GPL(rtsx_pci_start_run);
int rtsx_pci_write_register(struct rtsx_pcr *pcr, u16 addr, u8 mask, u8 data)
{
int i;
u32 val = HAIMR_WRITE_START;
val |= (u32)(addr & 0x3FFF) << 16;
val |= (u32)mask << 8;
val |= (u32)data;
rtsx_pci_writel(pcr, RTSX_HAIMR, val);
for (i = 0; i < MAX_RW_REG_CNT; i++) {
val = rtsx_pci_readl(pcr, RTSX_HAIMR);
if ((val & HAIMR_TRANS_END) == 0) {
if (data != (u8)val)
return -EIO;
return 0;
}
}
|