#include "fw.h"
 #include "tx.h"
 #include "reg.h"
+#include "sec.h"
 #include "debug.h"
 
 static void rtw_fw_c2h_cmd_handle_ext(struct rtw_dev *rtwdev,
        return location;
 }
 
+void rtw_fw_set_pg_info(struct rtw_dev *rtwdev)
+{
+       struct rtw_lps_conf *conf = &rtwdev->lps_conf;
+       u8 h2c_pkt[H2C_PKT_SIZE] = {0};
+       u8 loc_pg, loc_dpk;
+
+       loc_pg = rtw_get_rsvd_page_location(rtwdev, RSVD_LPS_PG_INFO);
+       loc_dpk = rtw_get_rsvd_page_location(rtwdev, RSVD_LPS_PG_DPK);
+
+       SET_H2C_CMD_ID_CLASS(h2c_pkt, H2C_CMD_LPS_PG_INFO);
+
+       LPS_PG_INFO_LOC(h2c_pkt, loc_pg);
+       LPS_PG_DPK_LOC(h2c_pkt, loc_dpk);
+       LPS_PG_SEC_CAM_EN(h2c_pkt, conf->sec_cam_backup);
+
+       rtw_fw_send_h2c_command(rtwdev, h2c_pkt);
+}
+
 void rtw_send_rsvd_page_h2c(struct rtw_dev *rtwdev)
 {
        u8 h2c_pkt[H2C_PKT_SIZE] = {0};
        return skb_new;
 }
 
+static struct sk_buff *rtw_lps_pg_dpk_get(struct ieee80211_hw *hw)
+{
+       struct rtw_dev *rtwdev = hw->priv;
+       struct rtw_chip_info *chip = rtwdev->chip;
+       struct rtw_dpk_info *dpk_info = &rtwdev->dm_info.dpk_info;
+       struct rtw_lps_pg_dpk_hdr *dpk_hdr;
+       struct sk_buff *skb;
+       u32 size;
+
+       size = chip->tx_pkt_desc_sz + sizeof(*dpk_hdr);
+       skb = alloc_skb(size, GFP_KERNEL);
+       if (!skb)
+               return NULL;
+
+       skb_reserve(skb, chip->tx_pkt_desc_sz);
+       dpk_hdr = skb_put_zero(skb, sizeof(*dpk_hdr));
+       dpk_hdr->dpk_ch = dpk_info->dpk_ch;
+       dpk_hdr->dpk_path_ok = dpk_info->dpk_path_ok[0];
+       memcpy(dpk_hdr->dpk_txagc, dpk_info->dpk_txagc, 2);
+       memcpy(dpk_hdr->dpk_gs, dpk_info->dpk_gs, 4);
+       memcpy(dpk_hdr->coef, dpk_info->coef, 160);
+
+       return skb;
+}
+
+static struct sk_buff *rtw_lps_pg_info_get(struct ieee80211_hw *hw,
+                                          struct ieee80211_vif *vif)
+{
+       struct rtw_dev *rtwdev = hw->priv;
+       struct rtw_chip_info *chip = rtwdev->chip;
+       struct rtw_lps_conf *conf = &rtwdev->lps_conf;
+       struct rtw_lps_pg_info_hdr *pg_info_hdr;
+       struct sk_buff *skb;
+       u32 size;
+
+       size = chip->tx_pkt_desc_sz + sizeof(*pg_info_hdr);
+       skb = alloc_skb(size, GFP_KERNEL);
+       if (!skb)
+               return NULL;
+
+       skb_reserve(skb, chip->tx_pkt_desc_sz);
+       pg_info_hdr = skb_put_zero(skb, sizeof(*pg_info_hdr));
+       pg_info_hdr->tx_bu_page_count = rtwdev->fifo.rsvd_drv_pg_num;
+       pg_info_hdr->macid = find_first_bit(rtwdev->mac_id_map, RTW_MAX_MAC_ID_NUM);
+       pg_info_hdr->sec_cam_count =
+               rtw_sec_cam_pg_backup(rtwdev, pg_info_hdr->sec_cam);
+
+       conf->sec_cam_backup = pg_info_hdr->sec_cam_count != 0;
+
+       return skb;
+}
+
 static struct sk_buff *rtw_get_rsvd_page_skb(struct ieee80211_hw *hw,
                                             struct ieee80211_vif *vif,
                                             enum rtw_rsvd_packet_type type)
        case RSVD_QOS_NULL:
                skb_new = ieee80211_nullfunc_get(hw, vif, true);
                break;
+       case RSVD_LPS_PG_DPK:
+               skb_new = rtw_lps_pg_dpk_get(hw);
+               break;
+       case RSVD_LPS_PG_INFO:
+               skb_new = rtw_lps_pg_info_get(hw, vif);
+               break;
        default:
                return NULL;
        }
 
        RSVD_PROBE_RESP,
        RSVD_NULL,
        RSVD_QOS_NULL,
+       RSVD_LPS_PG_DPK,
+       RSVD_LPS_PG_INFO,
 };
 
 enum rtw_fw_rf_type {
        u8 segment_iqk;
 };
 
+struct rtw_lps_pg_dpk_hdr {
+       u16 dpk_path_ok;
+       u8 dpk_txagc[2];
+       u16 dpk_gs[2];
+       u32 coef[2][20];
+       u8 dpk_ch;
+} __packed;
+
+struct rtw_lps_pg_info_hdr {
+       u8 macid;
+       u8 mbssid;
+       u8 pattern_count;
+       u8 mu_tab_group_id;
+       u8 sec_cam_count;
+       u8 tx_bu_page_count;
+       u16 rsvd;
+       u8 sec_cam[MAX_PG_CAM_BACKUP_NUM];
+} __packed;
+
 struct rtw_rsvd_page {
        struct list_head list;
        struct sk_buff *skb;
 #define H2C_CMD_RSVD_PAGE              0x0
 #define H2C_CMD_MEDIA_STATUS_RPT       0x01
 #define H2C_CMD_SET_PWR_MODE           0x20
+#define H2C_CMD_LPS_PG_INFO            0x2b
 #define H2C_CMD_RA_INFO                        0x40
 #define H2C_CMD_RSSI_MONITOR           0x42
 
        le32p_replace_bits((__le32 *)(h2c_pkt) + 0x01, value, GENMASK(7, 5))
 #define SET_PWR_MODE_SET_PWR_STATE(h2c_pkt, value)                             \
        le32p_replace_bits((__le32 *)(h2c_pkt) + 0x01, value, GENMASK(15, 8))
+#define LPS_PG_INFO_LOC(h2c_pkt, value)                                        \
+       le32p_replace_bits((__le32 *)(h2c_pkt) + 0x00, value, GENMASK(23, 16))
+#define LPS_PG_DPK_LOC(h2c_pkt, value)                                         \
+       le32p_replace_bits((__le32 *)(h2c_pkt) + 0x00, value, GENMASK(31, 24))
+#define LPS_PG_SEC_CAM_EN(h2c_pkt, value)                                      \
+       le32p_replace_bits((__le32 *)(h2c_pkt) + 0x00, value, BIT(8))
 #define SET_RSSI_INFO_MACID(h2c_pkt, value)                                    \
        le32p_replace_bits((__le32 *)(h2c_pkt) + 0x00, value, GENMASK(15, 8))
 #define SET_RSSI_INFO_RSSI(h2c_pkt, value)                                     \
 
 void rtw_fw_do_iqk(struct rtw_dev *rtwdev, struct rtw_iqk_para *para);
 void rtw_fw_set_pwr_mode(struct rtw_dev *rtwdev);
+void rtw_fw_set_pg_info(struct rtw_dev *rtwdev);
 void rtw_fw_query_bt_info(struct rtw_dev *rtwdev);
 void rtw_fw_wl_ch_info(struct rtw_dev *rtwdev, u8 link, u8 ch, u8 bw);
 void rtw_fw_query_bt_mp_info(struct rtw_dev *rtwdev,
 
                        rtw_add_rsvd_page(rtwdev, RSVD_PS_POLL, true);
                        rtw_add_rsvd_page(rtwdev, RSVD_QOS_NULL, true);
                        rtw_add_rsvd_page(rtwdev, RSVD_NULL, true);
+                       rtw_add_rsvd_page(rtwdev, RSVD_LPS_PG_DPK, true);
+                       rtw_add_rsvd_page(rtwdev, RSVD_LPS_PG_INFO, true);
                        rtw_fw_download_rsvd_page(rtwdev, vif);
                        rtw_send_rsvd_page_h2c(rtwdev);
                        rtw_coex_media_status_notify(rtwdev, conf->assoc);
                break;
        }
 
+       /* download new cam settings for PG to backup */
+       if (rtw_fw_lps_deep_mode == LPS_DEEP_MODE_PG)
+               rtw_fw_download_rsvd_page(rtwdev, vif);
+
 out:
        mutex_unlock(&rtwdev->mutex);
 
 
 
 #define RTW_MAX_MAC_ID_NUM             32
 #define RTW_MAX_SEC_CAM_NUM            32
+#define MAX_PG_CAM_BACKUP_NUM          8
 
 #define RTW_WATCH_DOG_DELAY_TIME       round_jiffies_relative(HZ * 2)
 
 enum rtw_lps_deep_mode {
        LPS_DEEP_MODE_NONE      = 0,
        LPS_DEEP_MODE_LCLK      = 1,
+       LPS_DEEP_MODE_PG        = 2,
 };
 
 enum rtw_pwr_state {
        u8 rlbm;
        u8 smart_ps;
        u8 port_id;
+       bool sec_cam_backup;
 };
 
 enum rtw_hw_key_type {
 
 
        /* toggle to request power mode, others remain 0 */
        request ^= request | BIT_RPWM_TOGGLE;
-       if (!enter)
+       if (!enter) {
                request |= POWER_MODE_ACK;
-       else
+       } else {
                request |= POWER_MODE_LCLK;
+               if (rtw_fw_lps_deep_mode == LPS_DEEP_MODE_PG)
+                       request |= POWER_MODE_PG;
+       }
 
        rtw_write8(rtwdev, rtwdev->hci.rpwm_addr, request);
 
                return;
        }
 
+       if (rtw_fw_lps_deep_mode == LPS_DEEP_MODE_PG)
+               rtw_fw_set_pg_info(rtwdev);
+
        rtw_hci_deep_ps(rtwdev, true);
 }
 
 
 #define RTW_LPS_THRESHOLD      2
 
 #define POWER_MODE_ACK         BIT(6)
+#define POWER_MODE_PG          BIT(4)
 #define POWER_MODE_LCLK                BIT(0)
 
 int rtw_enter_ips(struct rtw_dev *rtwdev);
 
        .dig_min = 0x20,
        .ht_supported = true,
        .vht_supported = true,
-       .lps_deep_mode_supported = BIT(LPS_DEEP_MODE_LCLK),
+       .lps_deep_mode_supported = BIT(LPS_DEEP_MODE_LCLK) | BIT(LPS_DEEP_MODE_PG),
        .sys_func_en = 0xD8,
        .pwr_on_seq = card_enable_flow_8822c,
        .pwr_off_seq = card_disable_flow_8822c,
 
        rtw_write32(rtwdev, RTW_SEC_CMD_REG, command);
 }
 
+u8 rtw_sec_cam_pg_backup(struct rtw_dev *rtwdev, u8 *used_cam)
+{
+       struct rtw_sec_desc *sec = &rtwdev->sec;
+       u8 offset = 0;
+       u8 count, n;
+
+       if (!used_cam)
+               return 0;
+
+       for (count = 0; count < MAX_PG_CAM_BACKUP_NUM; count++) {
+               n = find_next_bit(sec->cam_map, RTW_MAX_SEC_CAM_NUM, offset);
+               if (n == RTW_MAX_SEC_CAM_NUM)
+                       break;
+
+               used_cam[count] = n;
+               offset = n + 1;
+       }
+
+       return count;
+}
+
 void rtw_sec_enable_sec_engine(struct rtw_dev *rtwdev)
 {
        struct rtw_sec_desc *sec = &rtwdev->sec;
 
 void rtw_sec_clear_cam(struct rtw_dev *rtwdev,
                       struct rtw_sec_desc *sec,
                       u8 hw_key_idx);
+u8 rtw_sec_cam_pg_backup(struct rtw_dev *rtwdev, u8 *used_cam);
 void rtw_sec_enable_sec_engine(struct rtw_dev *rtwdev);
 
 #endif