[PATCH] mfd: Add support for RTS5250S power saving

From: rui_feng
Date: Thu Aug 17 2017 - 02:46:20 EST


From: rui_feng <rui_feng@xxxxxxxxxxxxxx>

Signed-off-by: rui_feng <rui_feng@xxxxxxxxxxxxxx>
---
drivers/mfd/rts5249.c | 139 +++++++++++++++++++++++++++++
drivers/mfd/rtsx_pcr.c | 206 +++++++++++++++++++++++++++++++++++++++++--
drivers/mfd/rtsx_pcr.h | 2 +
include/linux/mfd/rtsx_pci.h | 47 ++++++++++
4 files changed, 389 insertions(+), 5 deletions(-)

diff --git a/drivers/mfd/rts5249.c b/drivers/mfd/rts5249.c
index 40f8bb1..7873efc 100644
--- a/drivers/mfd/rts5249.c
+++ b/drivers/mfd/rts5249.c
@@ -103,8 +103,70 @@ static void rtsx_base_force_power_down(struct rtsx_pcr *pcr, u8 pm_state)
rtsx_pci_write_register(pcr, FPDCTL, 0x03, 0x03);
}

+static int rts5249_init_from_cfg(struct rtsx_pcr *pcr)
+{
+ struct cr_option *option = &(pcr->option);
+ u32 lval;
+
+ if (CHK_PCI_PID(pcr, 0x524A))
+ rtsx_pci_read_config_dword(pcr, 0x160, &lval);
+ else
+ rtsx_pci_read_config_dword(pcr, 0x168, &lval);
+
+ if (lval & (1 << 3))
+ set_dev_flag(pcr, ASPM_L1_1_EN);
+ else
+ clear_dev_flag(pcr, ASPM_L1_1_EN);
+ if (lval & (1 << 2))
+ set_dev_flag(pcr, ASPM_L1_2_EN);
+ else
+ clear_dev_flag(pcr, ASPM_L1_2_EN);
+
+ if (lval & (1 << 1))
+ set_dev_flag(pcr, PM_L1_1_EN);
+ else
+ clear_dev_flag(pcr, PM_L1_1_EN);
+ if (lval & (1 << 0))
+ set_dev_flag(pcr, PM_L1_2_EN);
+ else
+ clear_dev_flag(pcr, PM_L1_2_EN);
+
+ if (option->ltr_en) {
+ u16 val;
+
+ pcie_capability_read_word(pcr->pci, PCI_EXP_DEVCTL2, &val);
+ if (val & PCI_EXP_DEVCTL2_LTR_EN) {
+ option->ltr_enabled = 1;
+ option->ltr_active = 1;
+ rtsx_set_ltr_latency(pcr, option->ltr_active_latency);
+ } else {
+ option->ltr_enabled = 0;
+ }
+ }
+
+ return 0;
+}
+
+static int rts5249_init_from_hw(struct rtsx_pcr *pcr)
+{
+ struct cr_option *option = &(pcr->option);
+
+ if (check_dev_flag(pcr, ASPM_L1_1_EN | ASPM_L1_2_EN
+ | PM_L1_1_EN | PM_L1_2_EN))
+ option->force_clkreq_0 = 0;
+ else
+ option->force_clkreq_0 = 1;
+
+ return 0;
+}
+
static int rts5249_extra_init_hw(struct rtsx_pcr *pcr)
{
+ struct cr_option *option = &(pcr->option);
+
+ rts5249_init_from_cfg(pcr);
+ rts5249_init_from_hw(pcr);
+
rtsx_pci_init_cmd(pcr);

/* Rest L1SUB Config */
@@ -125,6 +187,14 @@ static int rts5249_extra_init_hw(struct rtsx_pcr *pcr)
else
rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PETXCFG, 0xB0, 0x80);

+ /* u_force_clkreq_0
+ * If 1, CLKREQ# PIN will be forced to drive 0 to request clock
+ */
+ if (option->force_clkreq_0)
+ rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PETXCFG, 0x80, 0x80);
+ else
+ rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PETXCFG, 0x80, 0x00);
+
return rtsx_pci_send_cmd(pcr, 100);
}

@@ -353,6 +423,8 @@ static int rtsx_base_switch_output_voltage(struct rtsx_pcr *pcr, u8 voltage)

void rts5249_init_params(struct rtsx_pcr *pcr)
{
+ struct cr_option *option = &(pcr->option);
+
pcr->extra_caps = EXTRA_CAPS_SD_SDR50 | EXTRA_CAPS_SD_SDR104;
pcr->num_slots = 2;
pcr->ops = &rts5249_pcr_ops;
@@ -372,6 +444,19 @@ void rts5249_init_params(struct rtsx_pcr *pcr)
pcr->ms_pull_ctl_disable_tbl = rts5249_ms_pull_ctl_disable_tbl;

pcr->reg_pm_ctrl3 = PM_CTRL3;
+
+ option->dev_flags |= LTR_L1SS_PWR_GATE_CHECK_CARD_EN;
+ option->dev_flags |= LTR_L1SS_PWR_GATE_EN;
+ option->ltr_en = 1;
+ /* init latency of active, idle, L1OFF to 60us, 300us, 3ms */
+ /* 60us:0x883C, 300us:0x892C, 3ms:0x9003 */
+ option->ltr_active_latency = 0x883C;
+ option->ltr_idle_latency = 0x892C;
+ option->ltr_l1off_latency = 0x9003;
+ option->dev_aspm_mode = DEV_ASPM_DYNAMIC;
+ option->l1_snooze_delay = 1;
+ option->ltr_l1off_sspwrgate = 0xAF;
+ option->ltr_l1off_snooze_sspwrgate = 0xAC;
}

static int rts524a_write_phy(struct rtsx_pcr *pcr, u8 addr, u16 val)
@@ -459,6 +544,54 @@ static int rts524a_extra_init_hw(struct rtsx_pcr *pcr)
return 0;
}

+static void rts5250_set_l1off_cfg_sub_D0(struct rtsx_pcr *pcr, int active)
+{
+ struct cr_option *option = &(pcr->option);
+
+ u32 interrupt = rtsx_pci_readl(pcr, RTSX_BIPR);
+ int card_exist = (interrupt & SD_EXIST) | (interrupt & MS_EXIST);
+ int aspm_L1_1, aspm_L1_2;
+
+ aspm_L1_1 = check_dev_flag(pcr, ASPM_L1_1_EN);
+ aspm_L1_2 = check_dev_flag(pcr, ASPM_L1_2_EN);
+
+ if (active) {
+ /* run, latency: 60us */
+ if (aspm_L1_1) {
+ u8 val = option->ltr_l1off_snooze_sspwrgate;
+
+ if (check_dev_flag(pcr,
+ LTR_L1SS_PWR_GATE_CHECK_CARD_EN)) {
+ if (card_exist)
+ val &= ~(1 << 7);
+ else
+ val |= (1 << 7);
+ }
+
+ rtsx_set_l1off_sub(pcr, val);
+ } else {
+ rtsx_set_l1off_sub(pcr, 0);
+ }
+ } else {
+ /* l1off, latency: 300us */
+ if (aspm_L1_2) {
+ u8 val = option->ltr_l1off_sspwrgate;
+
+ if (check_dev_flag(pcr,
+ LTR_L1SS_PWR_GATE_CHECK_CARD_EN)) {
+ if (card_exist)
+ val &= ~(1 << 7);
+ else
+ val |= (1 << 7);
+ }
+
+ rtsx_set_l1off_sub(pcr, val);
+ } else {
+ rtsx_set_l1off_sub(pcr, 0);
+ }
+ }
+}
+
static const struct pcr_ops rts524a_pcr_ops = {
.write_phy = rts524a_write_phy,
.read_phy = rts524a_read_phy,
@@ -473,11 +606,14 @@ static int rts524a_extra_init_hw(struct rtsx_pcr *pcr)
.card_power_off = rtsx_base_card_power_off,
.switch_output_voltage = rtsx_base_switch_output_voltage,
.force_power_down = rtsx_base_force_power_down,
+ .set_l1off_cfg_sub_D0 = rts5250_set_l1off_cfg_sub_D0,
};

void rts524a_init_params(struct rtsx_pcr *pcr)
{
rts5249_init_params(pcr);
+ pcr->option.ltr_l1off_sspwrgate = 0xFF;
+ pcr->option.ltr_l1off_snooze_sspwrgate = 0xF8;

pcr->reg_pm_ctrl3 = RTS524A_PM_CTRL3;
pcr->ops = &rts524a_pcr_ops;
@@ -576,11 +712,14 @@ static int rts525a_extra_init_hw(struct rtsx_pcr *pcr)
.card_power_off = rtsx_base_card_power_off,
.switch_output_voltage = rts525a_switch_output_voltage,
.force_power_down = rtsx_base_force_power_down,
+ .set_l1off_cfg_sub_D0 = rts5250_set_l1off_cfg_sub_D0,
};

void rts525a_init_params(struct rtsx_pcr *pcr)
{
rts5249_init_params(pcr);
+ pcr->option.ltr_l1off_sspwrgate = 0xFF;
+ pcr->option.ltr_l1off_snooze_sspwrgate = 0xF8;

pcr->reg_pm_ctrl3 = RTS524A_PM_CTRL3;
pcr->ops = &rts525a_pcr_ops;
diff --git a/drivers/mfd/rtsx_pcr.c b/drivers/mfd/rtsx_pcr.c
index a0ac89d..eb2255b 100644
--- a/drivers/mfd/rtsx_pcr.c
+++ b/drivers/mfd/rtsx_pcr.c
@@ -79,6 +79,143 @@ static inline void rtsx_pci_disable_aspm(struct rtsx_pcr *pcr)
0xFC, 0);
}

+int rtsx_comm_set_ltr_latency(struct rtsx_pcr *pcr, u32 latency)
+{
+ rtsx_pci_write_register(pcr, MSGTXDATA0, 0xFF, (u8) (latency & 0xFF));
+ rtsx_pci_write_register(pcr, MSGTXDATA1,
+ 0xFF, (u8)((latency >> 8) & 0xFF));
+ rtsx_pci_write_register(pcr, MSGTXDATA2,
+ 0xFF, (u8)((latency >> 16) & 0xFF));
+ rtsx_pci_write_register(pcr, MSGTXDATA3,
+ 0xFF, (u8)((latency >> 24) & 0xFF));
+ rtsx_pci_write_register(pcr, LTR_CTL, 0xC0, 0xC0);
+
+ 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);
+
+ return 0;
+}
+
+static void rtsx_comm_disable_aspm(struct rtsx_pcr *pcr)
+{
+ struct cr_option *option = &pcr->option;
+
+ if (pcr->aspm_enabled) {
+ if (option->dev_aspm_mode != DEV_ASPM_DISABLE) {
+ if (option->dev_aspm_mode == DEV_ASPM_DYNAMIC) {
+ rtsx_pci_disable_aspm(pcr);
+ } else if (option->dev_aspm_mode == DEV_ASPM_BACKDOOR) {
+ u8 mask = FORCE_ASPM_VAL_MASK;
+
+ rtsx_pci_write_register(pcr, ASPM_FORCE_CTL,
+ mask, 0);
+ }
+ msleep(20);
+ }
+ pcr->aspm_enabled = 0;
+ }
+}
+
+void rtsx_disable_aspm(struct rtsx_pcr *pcr)
+{
+ if (pcr->ops->disable_aspm)
+ pcr->ops->disable_aspm(pcr);
+ else
+ rtsx_comm_disable_aspm(pcr);
+}
+
+int rtsx_set_l1off_sub(struct rtsx_pcr *pcr, u8 val)
+{
+ rtsx_pci_write_register(pcr, L1SUB_CONFIG3, 0xFF, val);
+ return 0;
+}
+
+static void rtsx_comm_set_l1off_cfg_sub_D0(struct rtsx_pcr *pcr, int active)
+{
+ struct cr_option *option = &(pcr->option);
+ u32 interrupt = rtsx_pci_readl(pcr, RTSX_BIPR);
+ int card_exist = (interrupt & SD_EXIST) | (interrupt & MS_EXIST);
+ int aspm_L1_1, aspm_L1_2;
+
+ aspm_L1_1 = check_dev_flag(pcr, ASPM_L1_1_EN);
+ aspm_L1_2 = check_dev_flag(pcr, ASPM_L1_2_EN);
+
+ if (active) {
+ /* run, latency: 60us */
+ if (aspm_L1_1) {
+ u8 val = option->ltr_l1off_snooze_sspwrgate;
+
+ if (check_dev_flag(pcr,
+ LTR_L1SS_PWR_GATE_CHECK_CARD_EN)) {
+ if (card_exist)
+ val &= ~(1 << 4);
+ else
+ val |= (1 << 4);
+ }
+
+ rtsx_set_l1off_sub(pcr, val);
+ } else {
+ rtsx_set_l1off_sub(pcr, 0);
+ }
+ } else {
+ /* l1off, latency: 300us */
+ if (aspm_L1_2) {
+ u8 val = option->ltr_l1off_sspwrgate;
+
+ if (check_dev_flag(pcr,
+ LTR_L1SS_PWR_GATE_CHECK_CARD_EN)) {
+ if (card_exist)
+ val &= ~(1 << 4);
+ else
+ val |= (1 << 4);
+ }
+
+ rtsx_set_l1off_sub(pcr, val);
+ } else {
+ rtsx_set_l1off_sub(pcr, 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);
+ else
+ rtsx_comm_set_l1off_cfg_sub_D0(pcr, active);
+}
+
+static void rtsx_comm_pm_full_on(struct rtsx_pcr *pcr)
+{
+ struct cr_option *option = &pcr->option;
+
+ rtsx_disable_aspm(pcr);
+
+ if (option->ltr_enabled) {
+ u32 latency = option->ltr_active_latency;
+
+ rtsx_set_ltr_latency(pcr, latency);
+ }
+
+ if (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 */
@@ -89,9 +226,7 @@ void rtsx_pci_start_run(struct rtsx_pcr *pcr)
pcr->state = PDEV_STAT_RUN;
if (pcr->ops->enable_auto_blink)
pcr->ops->enable_auto_blink(pcr);
-
- if (pcr->aspm_en)
- rtsx_pci_disable_aspm(pcr);
+ rtsx_pm_full_on(pcr);
}

mod_delayed_work(system_wq, &pcr->idle_work, msecs_to_jiffies(200));
@@ -958,6 +1093,63 @@ static int rtsx_pci_acquire_irq(struct rtsx_pcr *pcr)
return 0;
}

+static void rtsx_comm_enable_aspm(struct rtsx_pcr *pcr)
+{
+ struct cr_option *option = &pcr->option;
+
+ if (!pcr->aspm_enabled) {
+ if (option->dev_aspm_mode != DEV_ASPM_DISABLE) {
+ u8 val = 0;
+
+ if (option->dev_aspm_mode == DEV_ASPM_DYNAMIC) {
+ rtsx_pci_enable_aspm(pcr);
+ } else if (option->dev_aspm_mode == DEV_ASPM_BACKDOOR) {
+ u8 mask = FORCE_ASPM_VAL_MASK;
+
+ val = pcr->aspm_en;
+ rtsx_pci_write_register(pcr, ASPM_FORCE_CTL,
+ mask, val);
+ }
+ }
+ pcr->aspm_enabled = 1;
+ }
+}
+
+void rtsx_enable_aspm(struct rtsx_pcr *pcr)
+{
+ if (pcr->ops->enable_aspm)
+ pcr->ops->enable_aspm(pcr);
+ else
+ rtsx_comm_enable_aspm(pcr);
+}
+
+static void rtsx_comm_pm_power_saving(struct rtsx_pcr *pcr)
+{
+ struct cr_option *option = &pcr->option;
+
+ if (option->ltr_enabled) {
+ u32 latency = option->ltr_l1off_latency;
+
+ if (check_dev_flag(pcr, L1_SNOOZE_TEST_EN))
+ mdelay(option->l1_snooze_delay);
+
+ rtsx_set_ltr_latency(pcr, latency);
+ }
+
+ if (check_dev_flag(pcr, LTR_L1SS_PWR_GATE_EN))
+ rtsx_set_l1off_sub_cfg_D0(pcr, 0);
+
+ rtsx_enable_aspm(pcr);
+}
+
+void rtsx_pm_power_saving(struct rtsx_pcr *pcr)
+{
+ if (pcr->ops->power_saving)
+ pcr->ops->power_saving(pcr);
+ else
+ rtsx_comm_pm_power_saving(pcr);
+}
+
static void rtsx_pci_idle_work(struct work_struct *work)
{
struct delayed_work *dwork = to_delayed_work(work);
@@ -974,8 +1166,7 @@ static void rtsx_pci_idle_work(struct work_struct *work)
if (pcr->ops->turn_off_led)
pcr->ops->turn_off_led(pcr);

- if (pcr->aspm_en)
- rtsx_pci_enable_aspm(pcr);
+ rtsx_pm_power_saving(pcr);

mutex_unlock(&pcr->pcr_mutex);
}
@@ -1063,6 +1254,11 @@ static int rtsx_pci_init_hw(struct rtsx_pcr *pcr)
if (err < 0)
return err;

+ if (CHK_PCI_PID(pcr, 0x5250) ||
+ CHK_PCI_PID(pcr, 0x524A) ||
+ CHK_PCI_PID(pcr, 0x525A))
+ rtsx_pci_write_register(pcr, PM_CLK_FORCE_CTL, 1, 1);
+
/* Enable clk_request_n to enable clock power management */
rtsx_pci_write_config_byte(pcr, pcr->pcie_cap + PCI_EXP_LNKCTL + 1, 1);
/* Enter L1 when host tx idle */
diff --git a/drivers/mfd/rtsx_pcr.h b/drivers/mfd/rtsx_pcr.h
index 931d1ae..544f82b 100644
--- a/drivers/mfd/rtsx_pcr.h
+++ b/drivers/mfd/rtsx_pcr.h
@@ -85,5 +85,7 @@ static inline u8 map_sd_drive(int idx)

/* generic operations */
int rtsx_gops_pm_reset(struct rtsx_pcr *pcr);
+int rtsx_set_ltr_latency(struct rtsx_pcr *pcr, u32 latency);
+int rtsx_set_l1off_sub(struct rtsx_pcr *pcr, u8 val);

#endif
diff --git a/include/linux/mfd/rtsx_pci.h b/include/linux/mfd/rtsx_pci.h
index 116816f..f7efb0f 100644
--- a/include/linux/mfd/rtsx_pci.h
+++ b/include/linux/mfd/rtsx_pci.h
@@ -876,14 +876,60 @@ struct pcr_ops {
int (*conv_clk_and_div_n)(int clk, int dir);
void (*fetch_vendor_settings)(struct rtsx_pcr *pcr);
void (*force_power_down)(struct rtsx_pcr *pcr, u8 pm_state);
+
+ void (*enable_aspm)(struct rtsx_pcr *pcr);
+ void (*disable_aspm)(struct rtsx_pcr *pcr);
+ int (*set_ltr_latency)(struct rtsx_pcr *pcr, u32 latency);
+ int (*set_l1off_sub)(struct rtsx_pcr *pcr, u8 val);
+ void (*set_l1off_cfg_sub_D0)(struct rtsx_pcr *pcr, int active);
+ void (*full_on)(struct rtsx_pcr *pcr);
+ void (*power_saving)(struct rtsx_pcr *pcr);
};

enum PDEV_STAT {PDEV_STAT_IDLE, PDEV_STAT_RUN};

+#define ASPM_L1_1_EN (1 << 0)
+#define ASPM_L1_2_EN (1 << 1)
+#define PM_L1_1_EN (1 << 2)
+#define PM_L1_2_EN (1 << 3)
+#define LTR_L1SS_PWR_GATE_EN (1 << 4)
+#define L1_SNOOZE_TEST_EN (1 << 5)
+#define LTR_L1SS_PWR_GATE_CHECK_CARD_EN (1 << 6)
+
+enum dev_aspm_mode {
+ DEV_ASPM_DISABLE = 0,
+ DEV_ASPM_DYNAMIC = 1,
+ DEV_ASPM_BACKDOOR = 2,
+ DEV_ASPM_STATIC = 3,
+};
+
+struct cr_option {
+ u32 dev_flags;
+ int force_clkreq_0;
+ int ltr_en;
+ int ltr_enabled;
+ int ltr_active;
+ u32 ltr_active_latency;
+ u32 ltr_idle_latency;
+ u32 ltr_l1off_latency;
+ enum dev_aspm_mode dev_aspm_mode;
+ u32 l1_snooze_delay;
+ u8 ltr_l1off_sspwrgate;
+ u8 ltr_l1off_snooze_sspwrgate;
+};
+
+#define set_dev_flag(cr, flag) \
+ ((cr)->option.dev_flags |= (flag))
+#define clear_dev_flag(cr, flag) \
+ ((cr)->option.dev_flags &= ~(flag))
+#define check_dev_flag(cr, flag) \
+ ((cr)->option.dev_flags & (flag))
+
struct rtsx_pcr {
struct pci_dev *pci;
unsigned int id;
int pcie_cap;
+ struct cr_option option;

/* pci resources */
unsigned long addr;
@@ -940,6 +986,7 @@ struct rtsx_pcr {
u8 card_drive_sel;
#define ASPM_L1_EN 0x02
u8 aspm_en;
+ int aspm_enabled;

#define PCR_MS_PMOS (1 << 0)
#define PCR_REVERSE_SOCKET (1 << 1)
--
1.9.1